[protozero] 01/08: Imported Upstream version 1.4.0

Bas Couwenberg sebastic at debian.org
Fri Jul 22 14:39:23 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository protozero.

commit 523cc155bfdd75f449759fa0cc61ce46aab7345f
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Fri Jul 22 13:06:25 2016 +0200

    Imported Upstream version 1.4.0
---
 .travis.yml                                    |   2 +-
 CHANGELOG.md                                   |  18 +-
 CONTRIBUTING.md                                |   1 +
 Makefile                                       |  22 +-
 README.md                                      |  41 +-
 UPGRADING.md                                   |  66 +++
 doc/Doxyfile                                   |   4 +-
 doc/cheatsheet.md                              |  67 +++
 doc/macros.md                                  |  48 ++
 tutorial.md => doc/tutorial.md                 |   8 +-
 include/protozero/byteswap.hpp                 |   6 +-
 include/protozero/exception.hpp                |   8 +-
 include/protozero/iterators.hpp                | 373 ++++++++++++
 include/protozero/pbf_builder.hpp              |  20 +-
 include/protozero/pbf_message.hpp              |  10 +-
 include/protozero/pbf_reader.hpp               | 763 +++++++++----------------
 include/protozero/pbf_writer.hpp               | 240 +++++---
 include/protozero/types.hpp                    | 170 +++++-
 include/protozero/varint.hpp                   | 125 ++--
 include/protozero/version.hpp                  |  17 +-
 package.json                                   |   2 +-
 test/include/packed_access.hpp                 | 234 ++++++++
 test/include/scalar_access.hpp                 |  20 +
 test/include/test.hpp                          |   4 +
 test/t/basic/test_cases.cpp                    |  64 +--
 test/t/complex/test_cases.cpp                  |  56 +-
 test/t/data_view/test_cases.cpp                |  83 +++
 test/t/exceptions/test_cases.cpp               |  34 +-
 test/t/fixed32/data-pos200.pbf                 | Bin 0 -> 5 bytes
 test/t/fixed32/testcase.cpp                    |   3 +
 test/t/fixed64/data-pos200.pbf                 | Bin 0 -> 9 bytes
 test/t/fixed64/testcase.cpp                    |   3 +
 test/t/int32/data-neg200.pbf                   |   1 +
 test/t/int32/data-pos200.pbf                   |   1 +
 test/t/int32/testcase.cpp                      |   6 +
 test/t/int64/data-neg200.pbf                   |   1 +
 test/t/int64/data-pos200.pbf                   |   1 +
 test/t/int64/testcase.cpp                      |   6 +
 test/t/message/test_cases.cpp                  |   7 +
 test/t/repeated_packed_bool/test_cases.cpp     |  16 +-
 test/t/repeated_packed_double/test_cases.cpp   |  12 +-
 test/t/repeated_packed_enum/test_cases.cpp     |  16 +-
 test/t/repeated_packed_fixed32/data-many.pbf   | Bin 18 -> 22 bytes
 test/t/repeated_packed_fixed32/test_cases.cpp  | 202 +------
 test/t/repeated_packed_fixed32/testcase.cpp    |   1 +
 test/t/repeated_packed_fixed64/data-many.pbf   | Bin 34 -> 42 bytes
 test/t/repeated_packed_fixed64/test_cases.cpp  |  93 +--
 test/t/repeated_packed_fixed64/testcase.cpp    |   1 +
 test/t/repeated_packed_float/test_cases.cpp    |  12 +-
 test/t/repeated_packed_int32/data-many.pbf     | Bin 30 -> 42 bytes
 test/t/repeated_packed_int32/test_cases.cpp    |  86 +--
 test/t/repeated_packed_int32/testcase.cpp      |   4 +-
 test/t/repeated_packed_int64/data-many.pbf     | Bin 34 -> 46 bytes
 test/t/repeated_packed_int64/test_cases.cpp    | 122 +---
 test/t/repeated_packed_int64/testcase.cpp      |   4 +-
 test/t/repeated_packed_sfixed32/data-many.pbf  | Bin 26 -> 34 bytes
 test/t/repeated_packed_sfixed32/test_cases.cpp |  85 +--
 test/t/repeated_packed_sfixed32/testcase.cpp   |   4 +-
 test/t/repeated_packed_sfixed64/data-many.pbf  | Bin 50 -> 66 bytes
 test/t/repeated_packed_sfixed64/test_cases.cpp |  84 +--
 test/t/repeated_packed_sfixed64/testcase.cpp   |   4 +-
 test/t/repeated_packed_sint32/data-many.pbf    | Bin 16 -> 20 bytes
 test/t/repeated_packed_sint32/test_cases.cpp   |  84 +--
 test/t/repeated_packed_sint32/testcase.cpp     |   4 +-
 test/t/repeated_packed_sint64/data-many.pbf    | Bin 26 -> 30 bytes
 test/t/repeated_packed_sint64/test_cases.cpp   | 122 +---
 test/t/repeated_packed_sint64/testcase.cpp     |   4 +-
 test/t/repeated_packed_uint32/data-many.pbf    | Bin 10 -> 12 bytes
 test/t/repeated_packed_uint32/test_cases.cpp   |  82 +--
 test/t/repeated_packed_uint32/testcase.cpp     |   1 +
 test/t/repeated_packed_uint64/data-many.pbf    | Bin 15 -> 17 bytes
 test/t/repeated_packed_uint64/test_cases.cpp   |  82 +--
 test/t/repeated_packed_uint64/testcase.cpp     |   1 +
 test/t/rollback/test_cases.cpp                 |  12 +-
 test/t/sfixed32/data-neg200.pbf                |   1 +
 test/t/sfixed32/data-pos200.pbf                | Bin 0 -> 5 bytes
 test/t/sfixed32/testcase.cpp                   |   6 +
 test/t/sfixed64/data-neg200.pbf                |   1 +
 test/t/sfixed64/data-pos200.pbf                | Bin 0 -> 9 bytes
 test/t/sfixed64/testcase.cpp                   |   6 +
 test/t/sint32/data-neg200.pbf                  |   1 +
 test/t/sint32/data-pos200.pbf                  |   1 +
 test/t/sint32/testcase.cpp                     |   8 +-
 test/t/sint64/data-neg200.pbf                  |   1 +
 test/t/sint64/data-pos200.pbf                  |   1 +
 test/t/sint64/testcase.cpp                     |   6 +
 test/t/skip/test_cases.cpp                     | 228 ++++----
 test/t/string/test_cases.cpp                   |  49 +-
 test/t/uint32/data-pos200.pbf                  |   1 +
 test/t/uint32/testcase.cpp                     |   3 +
 test/t/uint64/data-pos200.pbf                  |   1 +
 test/t/uint64/testcase.cpp                     |   3 +
 test/t/varint/test_cases.cpp                   | 114 +++-
 test/t/vector_tile/test_cases.cpp              |  29 +-
 test/t/wrong_type_access/test_cases.cpp        |  16 +-
 test/t/zigzag/test_cases.cpp                   |  62 +-
 96 files changed, 2251 insertions(+), 1960 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 6ab690d..8a83b9c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,7 @@ addons_shortcuts:
       packages: [ 'clang-3.5', 'libprotobuf-dev','protobuf-compiler' ]
   addons_clang38: &clang38
     apt:
-      sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise' ]
+      sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8' ]
       packages: [ 'clang-3.8', 'libprotobuf-dev','protobuf-compiler' ]
   addons_gcc49: &gcc47
     apt:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db457d1..43dbe13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,21 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
+## [1.4.0] - 2016-07-22
+
+### Changed
+
+- Use more efficient new `skip_varint()` function when iterating over
+  packed varints.
+- Split `decode_varint()` function into two functions speeding up the
+  common case where a varint is only one byte long.
+- Introduce new class `iterator_range` used instead of `std::pair` of
+  iterators. This way the objects can be used in range-based for loops.
+  Read UPGRADING.md for details.
+- Introduce new class `data_view` and functions using and returning it.
+  Read UPGRADING.md for details.
+
+
 ## [1.3.0] - 2016-02-18
 
 ### Added
@@ -75,7 +90,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - Make pbf reader and writer code endianess-aware.
 
 
-[unreleased]: https://github.com/osmcode/libosmium/compare/v1.3.0...HEAD
+[unreleased]: https://github.com/osmcode/libosmium/compare/v1.4.0...HEAD
+[1.4.0]: https://github.com/osmcode/libosmium/compare/v1.3.0...v1.4.0
 [1.3.0]: https://github.com/osmcode/libosmium/compare/v1.2.3...v1.3.0
 [1.2.3]: https://github.com/osmcode/libosmium/compare/v1.2.2...v1.2.3
 [1.2.2]: https://github.com/osmcode/libosmium/compare/v1.2.1...v1.2.2
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9da5be3..b662b25 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,6 +9,7 @@ To release a new protozero version:
    - include/protozero/version.hpp
    - package.json
  - Update CHANGELOG.md
+ - Update UPGRADING.md
  - Create a new tag and push to github `git push --tags`
  - Publish to npm:
    - First run `make testpack` to see what files will be published
diff --git a/Makefile b/Makefile
index 83aeb39..5c72235 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,9 @@ CXX := $(CXX)
 CXXFLAGS := $(CXXFLAGS)
 LDFLAGS := $(LDFLAGS)
 
+# Installation directory
+DESTDIR ?= /usr
+
 WARNING_FLAGS := -Wall -Wextra -pedantic -Wsign-compare -Wsign-conversion -Wunused-parameter -Wno-float-equal -Wno-covered-switch-default
 
 ifneq ($(findstring clang,$(CXX)),)
@@ -38,18 +41,25 @@ PROTO_FILES_H := $(subst .proto,.pb.h,$(PROTO_FILES))
 PROTO_FILES_O := $(subst .proto,.pb.o,$(PROTO_FILES))
 
 HPP_FILES := include/protozero/byteswap.hpp \
+             include/protozero/config.hpp \
              include/protozero/exception.hpp \
+             include/protozero/iterators.hpp \
+             include/protozero/pbf_builder.hpp \
+             include/protozero/pbf_message.hpp \
+             include/protozero/pbf_reader.hpp \
+             include/protozero/pbf_writer.hpp \
              include/protozero/types.hpp \
              include/protozero/varint.hpp \
-             include/protozero/pbf_reader.hpp \
-             include/protozero/pbf_writer.hpp
+             include/protozero/version.hpp
+
+DOC_FILES = doc/cheatsheet.md doc/macros.md doc/tutorial.md
 
 CFLAGS_PROTOBUF := $(subst -I,-isystem ,$(shell pkg-config protobuf --cflags))
 LDFLAGS_PROTOBUF := $(shell pkg-config protobuf --libs-only-L)
 
 all: ./test/tests test/writer_tests
 
-./test/t/%/test_cases.o: test/t/%/test_cases.cpp test/include/test.hpp test/include/scalar_access.hpp $(HPP_FILES)
+./test/t/%/test_cases.o: test/t/%/test_cases.cpp test/include/test.hpp test/include/scalar_access.hpp test/include/packed_access.hpp $(HPP_FILES)
 	$(CXX) -c -Iinclude -Itest/include $(CXXFLAGS) $(COMMON_FLAGS) $(DEBUG_FLAGS) $< -o $@
 
 ./test/tests.o: test/tests.cpp $(HPP_FILES)
@@ -89,7 +99,7 @@ iwyu: $(HPP_FILES) test/tests.cpp test/writer_tests.cpp
 check: $(HPP_FILES) test/tests.cpp test/include/test.hpp test/include/testcase.hpp test/t/*/testcase.cpp $(TEST_CASES)
 	cppcheck -Uassert --std=c++11 --enable=all --suppress=incorrectStringBooleanError $^
 
-doc: doc/Doxyfile README.md tutorial.md $(HPP_FILES)
+doc: doc/Doxyfile README.md UPGRADING.md $(DOC_FILES) $(HPP_FILES)
 	doxygen doc/Doxyfile
 
 clean:
@@ -118,5 +128,9 @@ testpack:
 	tar -ztvf *tgz
 	rm -f ./*tgz
 
+install:
+	install -m 0755 -o root -g root -d $(DESTDIR)/include/protozero
+	install -m 0644 -o root -g root include/protozero/* $(DESTDIR)/include/protozero
+
 .PHONY: all test iwyu check doc
 
diff --git a/README.md b/README.md
index e575022..fef52e2 100644
--- a/README.md
+++ b/README.md
@@ -15,12 +15,9 @@ C++ API that can be generated via the Google Protobufs `protoc` program.
 [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/o354pq10y96mnr6d?svg=true)](https://ci.appveyor.com/project/Mapbox/protozero)
 [![Coverage Status](https://coveralls.io/repos/mapbox/protozero/badge.svg?branch=master&service=github)](https://coveralls.io/github/mapbox/protozero?branch=master)
 
-
 ## Depends
 
  - C++11 compiler
- - A working knowledge of how
-   [protocol buffer encoding works](https://developers.google.com/protocol-buffers/docs/encoding).
 
 
 ## How it works
@@ -41,13 +38,31 @@ information from the `.proto` description. This results in a few restrictions:
 The library will make sure not to overrun the buffer it was given, but
 basically all other checks have to be made in user code!
 
-See the [tutorial](tutorial.md) for more information on how to use it.
 
-Call `make doc` to build the Doxygen documentation. (You'll need
-[Doxygen](http://www.stack.nl/~dimitri/doxygen/) installed.) Then open
+## Documentation
+
+You have to have a working knowledge of how
+[protocol buffer encoding works](https://developers.google.com/protocol-buffers/docs/encoding).
+
+* Read the [tutorial](doc/tutorial.md) for an introduction on how to use
+  Protozero.
+* There is a table of all types and functions in the
+  [cheat sheet](doc/cheatsheet.md).
+* [Macros defined or used by Protozero](doc/macros.md).
+* Read the [upgrading instructions](UPGRADING.md) if you are upgrading from
+  an older version of Protozero.
+
+Call `make doc` to build the Doxygen-based reference documentation. (You'll
+need [Doxygen](http://www.stack.nl/~dimitri/doxygen/) installed.) Then open
 `doc/html/index.html` in your browser to read it.
 
 
+## Installation
+
+Call `make install` to install include files in `/usr/include/protozero`. Call
+`make install DESTDIR=/usr/local` or similar to change install directory.
+
+
 ## Limitations
 
 * A protobuf message has to fit into memory completely, otherwise it can not
@@ -133,3 +148,17 @@ For extra checks with [Cppcheck](http://cppcheck.sourceforge.net/) you can call
 
     make check
 
+
+## Who is using Protozero?
+
+* [Carmen](https://github.com/mapbox/carmen-cache)
+* [Libosmium](https://github.com/osmcode/libosmium)
+* [Mapnik](https://github.com/mapbox/mapnik-vector-tile)
+* [Mapbox GL Native](https://github.com/mapbox/mapbox-gl-native)
+* [OSRM](https://github.com/Project-OSRM/osrm-backend)
+* [Tippecanoe](https://github.com/mapbox/tippecanoe)
+
+Are you using Protozero? Tell us! Send a pull request with changes to this
+README.
+
+
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 0000000..22d198c
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,66 @@
+
+# Upgrading
+
+This file contains instructions for users of Protozero who are upgrading from
+one version to another.
+
+You do not need to change anything if only the minor version changes, but it
+is better to keep up with changes if you can. The switch to the next major
+version will be easier then. And you might get some more convenient usage.
+
+To help you with upgrading to new versions, you can define the C++ preprocessor
+macro `PROTOZERO_STRICT_API` in which case Protozero will compile without the
+code used for backwards compatibilty. You will then get compile errors for
+older API usages.
+
+## Upgrading from *v1.3.0* to *v1.4.0*
+
+* Functions in `pbf_reader` (and the derived `pbf_message`) called
+  `get_packed_*()` now return an `iterator_range` instead of a `std::pair`.
+  The new class is derived from `std::pair`, so changes are usually not
+  strictly necessary. For future compatibility, you should change all
+  attribute accesses on the returned objects from `first` and `second` to
+  `begin()` and `end()`, respectively. So change something like this:
+
+      auto x = message.get_packed_int32();
+      for (auto it = x.first; it != x.second; ++it) {
+          ....
+      }
+
+  to:
+
+      auto x = message.get_packed_int32();
+      for (auto it = x.begin(); it != x.end(); ++it) {
+          ....
+      }
+
+  or even better use the range-based for loop:
+
+      auto x = message.get_packed_int32();
+      for (auto val : x) {
+          ....
+      }
+
+  Ranges can also be used in this way. This will change the range in-place:
+
+      auto range = message.get_packed_int32();
+      while (!range.empty()) {
+          auto value = range.front();
+          range.drop_front();
+          ....
+      }
+
+* The class `pbf_reader` has a new method `get_view()` returning an object
+  of the new `protozero::data_view` class. The `data_view` only has minimal
+  functionality, but what it has is compatible to the `std::string_view` class
+  which will be coming in C++17. The view autoconverts to a `std::string` if
+  needed. Use `get_view()` instead of `get_data()` giving you a more intuitive
+  interface (call `data()` and `size()` on the view instead of using `first`
+  and `second` on the `std::pair` returned by `get_data()`).
+
+  You can set the macro `PROTOZERO_USE_VIEW` (before including `types.hpp`) to
+  the name of any class that behaves like `protozero::data_view` and
+  `data_view` will be an alias to that class instead of the implementation
+  from protozero. This way you can use the C++17 `string_view` or a similar
+  class if it is already available on your system.
+
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 14e0311..b4e20d4 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -753,7 +753,7 @@ WARN_LOGFILE           =
 # spaces.
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = README.md tutorial.md include/protozero
+INPUT                  = README.md UPGRADING.md doc include/protozero
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -815,7 +815,7 @@ EXCLUDE_PATTERNS       =
 # Note that the wildcards are matched against the file with absolute path, so to
 # exclude all test directories use the pattern */test/*
 
-EXCLUDE_SYMBOLS        =
+EXCLUDE_SYMBOLS        = protozero::detail protozero_assert PROTOZERO_*_ENDIAN PROTOZERO_BYTE_ORDER PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED
 
 # The EXAMPLE_PATH tag can be used to specify one or more files or directories
 # that contain example code fragments that are included (see the \include
diff --git a/doc/cheatsheet.md b/doc/cheatsheet.md
new file mode 100644
index 0000000..ef17f24
--- /dev/null
+++ b/doc/cheatsheet.md
@@ -0,0 +1,67 @@
+
+# Protozero Cheat Sheet
+
+See also this
+[handy table](https://developers.google.com/protocol-buffers/docs/proto#scalar)
+from the Google Protocol Buffers documentation.
+
+## Scalar Types
+
+| PBF Type | Underlying Storage | C++ Type      | Getter           | Notes |
+| -------- | ------------------ | ------------- | ---------------- | ----- |
+| int32    | varint             | `int32_t`     | `get_int32()`    |       |
+| sint32   | varint (zigzag)    | `int32_t`     | `get_sint32()`   |       |
+| uint32   | varint             | `uint32_t`    | `get_uint32()`   |       |
+| int64    | varint             | `int64_t`     | `get_int64()`    |       |
+| sint64   | varint (zigzag)    | `int64_t`     | `get_sint64()`   |       |
+| uint64   | varint             | `uint64_t`    | `get_uint64()`   |       |
+| bool     | varint             | `bool`        | `get_bool()`     |       |
+| enum     | varint             | `int32_t`     | `get_enum()`     |       |
+| fixed32  | 32bit fixed        | `uint32_t`    | `get_fixed32()`  |       |
+| sfixed32 | 32bit fixed        | `int32_t`     | `get_sfixed32()` |       |
+| fixed64  | 64bit fixed        | `uint64_t`    | `get_fixed64()`  |       |
+| sfixed64 | 64bit fixed        | `int64_t`     | `get_sfixed64()` |       |
+| float    | 32bit fixed        | `float`       | `get_float()`    |       |
+| double   | 64bit fixed        | `double`      | `get_double()`   |       |
+| string   | length-delimited   | `data_view`   | `get_view()`     | (1)   |
+| string   | length-delimited   | pair          | `get_data()`     | (2)   |
+| string   | length-delimited   | `std::string` | `get_string()`   |       |
+| bytes    | length-delimited   | `data_view`   | `get_view()`     | (1)   |
+| bytes    | length-delimited   | pair          | `get_data()`     | (2)   |
+| bytes    | length-delimited   | `std::string` | `get_bytes()`    |       |
+| message  | length-delimited   | `data_view`   | `get_view()`     | (1)   |
+| message  | length-delimited   | pair          | `get_data()`     | (2)   |
+| message  | length-delimited   | `pbf_reader`  | `get_message()`  |       |
+
+### Notes:
+
+* (1) preferred form, returns `protozero::data_view` which is convertible to
+  `std::string` if needed.
+* (2) deprecated form, returns `std::pair<const char*, pbf_length_type>`,
+  use `get_view()` instead. This form is only available if
+  `PROTOZERO_STRICT_API` is not defined.
+* The setter function of `pbf_writer` is always `add_` + the PBF type. Several
+  overloads are available.
+
+
+## Packed Repeated Fields
+
+| PBF Type | Getter                  |
+| -------- | ----------------------- |
+| int32    | `get_packed_int32()`    |
+| sint32   | `get_packed_sint32()`   |
+| uint32   | `get_packed_uint32()`   |
+| int64    | `get_packed_int64()`    |
+| sint64   | `get_packed_sint64()`   |
+| uint64   | `get_packed_uint64()`   |
+| bool     | `get_packed_bool()`     |
+| enum     | `get_packed_enum()`     |
+| fixed32  | `get_packed_fixed32()`  |
+| sfixed32 | `get_packed_sfixed32()` |
+| fixed64  | `get_packed_fixed64()`  |
+| sfixed64 | `get_packed_sfixed64()` |
+| float    | `get_packed_float()`    |
+| double   | `get_packed_double()`   |
+
+Packed repeated fields for string, bytes, and message types are not possible.
+
diff --git a/doc/macros.md b/doc/macros.md
new file mode 100644
index 0000000..4b9fe65
--- /dev/null
+++ b/doc/macros.md
@@ -0,0 +1,48 @@
+
+# Macros
+
+## Version number
+
+If `protozero/version.hpp` is included, the following macros are set:
+
+| Macro                      | Example | Description                                    |
+| -------------------------- | ------- | ---------------------------------------------- |
+| `PROTOZERO_VERSION_MAJOR`  | 1       | Major version number                           |
+| `PROTOZERO_VERSION_MINOR`  | 3       | Minor version number                           |
+| `PROTOZERO_VERSION_PATCH`  | 2       | Patch number                                   |
+| `PROTOZERO_VERSION_CODE`   | 10302   | Version (major * 10,000 + minor * 100 + patch) |
+| `PROTOZERO_VERSION_STRING` | "1.3.2" | Version string                                 |
+
+## Changing Protozero behaviour
+
+The behaviour of Protozero can be changed by defining the following macros.
+They have to be set before including any of the Protozero headers.
+
+## `PROTOZERO_STRICT_API`
+
+If this is set, you will get some extra warnings or errors during compilation
+if you are using an old (deprecated) interface to Protozero. Enable this if
+you want to make sure your code will work with future versions of Protozero.
+
+## `PROTOZERO_USE_VIEW`
+
+If this is unset `protozero::data_view` is Protozero's own implementation of
+a *string view* class.
+
+Set this if you want to use a different implementation such as the C++17
+`std::string_view` class. In this case `protozero::data_view` will simply be
+an alias to the class you specify.
+
+    #define PROTOZERO_USE_VIEW std::string_view
+
+This affects the result type of the `pbf_reader::get_view()` method and a few
+others taking a `protozero::data_view` as parameters.
+
+## `PROTOZERO_DO_NOT_USE_BARE_POINTER`
+
+Can be set to force Protozero to not use bare pointers for some iterators
+returned from `get_packed_*` calls. It is usually not necessary to use this
+and might affect performance if you do. If you are getting errors about
+unaligned memory access or a SIGBUS, you can try to set this. (Please also
+report on error if this is the case.)
+
diff --git a/tutorial.md b/doc/tutorial.md
similarity index 98%
rename from tutorial.md
rename to doc/tutorial.md
index c139739..58b2d43 100644
--- a/tutorial.md
+++ b/doc/tutorial.md
@@ -16,7 +16,7 @@ Protocol Buffer documentation:
 Make sure you understand the basic types of values supported by Protocol
 Buffers. Refer to this
 [handy table](https://developers.google.com/protocol-buffers/docs/proto#scalar)
-if you are getting lost.
+and [the cheat sheet](cheatsheet.md) if you are getting lost.
 
 
 ## Prerequisites
@@ -132,9 +132,9 @@ function called `get_` + _field type_.
 For `string` and `bytes` types the internal handling is exactly the same, but
 both `get_string()` and `get_bytes()` are provided to make the code
 self-documenting. Both theses calls allocate and return a `std::string` which
-can add some overhead. You can call the `get_data()` function instead which
-returns a `std::pair<const char*, uint32_t>`, ie a pointer into the data and
-the length of the data.
+can add some overhead. You can call the `get_view()` function instead which
+returns a `data_view` containing a pointer into the data (access with `data()`)
+and the length of the data (access with `size()`).
 
 
 ### Handling Repeated Packed Fields
diff --git a/include/protozero/byteswap.hpp b/include/protozero/byteswap.hpp
index a018c1c..06ba6de 100644
--- a/include/protozero/byteswap.hpp
+++ b/include/protozero/byteswap.hpp
@@ -28,7 +28,7 @@ namespace protozero {
  * be specialized to actually work.
  */
 template <int N>
-inline void byteswap(const char* /*data*/, char* /*result*/) {
+inline void byteswap(const char* /*data*/, char* /*result*/) noexcept {
     static_assert(N == 1, "Can only swap 4 or 8 byte values");
 }
 
@@ -36,7 +36,7 @@ inline void byteswap(const char* /*data*/, char* /*result*/) {
  * Swap 4 byte value (int32_t, uint32_t, float) between endianness formats.
  */
 template <>
-inline void byteswap<4>(const char* data, char* result) {
+inline void byteswap<4>(const char* data, char* result) noexcept {
 #ifdef PROTOZERO_USE_BUILTIN_BSWAP
     *reinterpret_cast<uint32_t*>(result) = __builtin_bswap32(*reinterpret_cast<const uint32_t*>(data));
 #else
@@ -51,7 +51,7 @@ inline void byteswap<4>(const char* data, char* result) {
  * Swap 8 byte value (int64_t, uint64_t, double) between endianness formats.
  */
 template <>
-inline void byteswap<8>(const char* data, char* result) {
+inline void byteswap<8>(const char* data, char* result) noexcept {
 #ifdef PROTOZERO_USE_BUILTIN_BSWAP
     *reinterpret_cast<uint64_t*>(result) = __builtin_bswap64(*reinterpret_cast<const uint64_t*>(data));
 #else
diff --git a/include/protozero/exception.hpp b/include/protozero/exception.hpp
index 5c7ab54..ca4340e 100644
--- a/include/protozero/exception.hpp
+++ b/include/protozero/exception.hpp
@@ -29,7 +29,7 @@ namespace protozero {
  */
 struct exception : std::exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "pbf exception"; }
+    const char* what() const noexcept override { return "pbf exception"; }
 };
 
 /**
@@ -38,7 +38,7 @@ struct exception : std::exception {
  */
 struct varint_too_long_exception : exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "varint too long exception"; }
+    const char* what() const noexcept override { return "varint too long exception"; }
 };
 
 /**
@@ -47,7 +47,7 @@ struct varint_too_long_exception : exception {
  */
 struct unknown_pbf_wire_type_exception : exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "unknown pbf field type exception"; }
+    const char* what() const noexcept override { return "unknown pbf field type exception"; }
 };
 
 /**
@@ -60,7 +60,7 @@ struct unknown_pbf_wire_type_exception : exception {
  */
 struct end_of_buffer_exception : exception {
     /// Returns the explanatory string.
-    const char *what() const noexcept override { return "end of buffer exception"; }
+    const char* what() const noexcept override { return "end of buffer exception"; }
 };
 
 } // end namespace protozero
diff --git a/include/protozero/iterators.hpp b/include/protozero/iterators.hpp
new file mode 100644
index 0000000..813d96b
--- /dev/null
+++ b/include/protozero/iterators.hpp
@@ -0,0 +1,373 @@
+#ifndef PROTOZERO_ITERATORS_HPP
+#define PROTOZERO_ITERATORS_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file iterators.hpp
+ *
+ * @brief Contains the iterators for access to packed repeated fields.
+ */
+
+#include <cstring>
+#include <iterator>
+#include <utility>
+
+#include <protozero/config.hpp>
+#include <protozero/varint.hpp>
+
+#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN
+# include <protozero/byteswap.hpp>
+#endif
+
+namespace protozero {
+
+namespace detail {
+
+    // Copy N bytes from src to dest on little endian machines, on big
+    // endian swap the bytes in the process.
+    template <int N>
+    inline void copy_or_byteswap(const char* src, void* dest) noexcept {
+#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
+        std::memcpy(dest, src, N);
+#else
+        byteswap<N>(src, reinterpret_cast<char*>(dest));
+#endif
+    }
+
+} // end namespace detail
+
+/**
+ * A range of iterators based on std::pair. Created from beginning and
+ * end iterators. Used as a return type from some pbf_reader methods
+ * that is easy to use with range-based for loops.
+ */
+template <typename T, typename P = std::pair<T, T>>
+class iterator_range :
+#ifdef PROTOZERO_STRICT_API
+    protected
+#else
+    public
+#endif
+        P {
+
+public:
+
+    /// The type of the iterators in this range.
+    using iterator = T;
+
+    /// The value type of the underlying iterator.
+    using value_type = typename std::iterator_traits<T>::value_type;
+
+    /**
+     * Default constructor. Create empty iterator_range.
+     */
+    constexpr iterator_range() :
+        P(iterator{}, iterator{}) {
+    }
+
+    /**
+     * Create iterator range from two iterators.
+     *
+     * @param first Iterator to beginning or range.
+     * @param last Iterator to end or range.
+     */
+    constexpr iterator_range(iterator&& first, iterator&& last) :
+        P(std::forward<iterator>(first),
+          std::forward<iterator>(last)) {
+    }
+
+    /// Return iterator to beginning of range.
+    constexpr iterator begin() const noexcept {
+        return this->first;
+    }
+
+    /// Return iterator to end of range.
+    constexpr iterator end() const noexcept {
+        return this->second;
+    }
+
+    /// Return iterator to beginning of range.
+    constexpr iterator cbegin() const noexcept {
+        return this->first;
+    }
+
+    /// Return iterator to end of range.
+    constexpr iterator cend() const noexcept {
+        return this->second;
+    }
+
+    /// Return true if this range is empty.
+    constexpr std::size_t empty() const noexcept {
+        return begin() == end();
+    }
+
+    /**
+     * Get element at the beginning of the range.
+     *
+     * @pre Range must not be empty.
+     */
+    value_type front() const {
+        protozero_assert(!empty());
+        return *(this->first);
+    }
+
+    /**
+     * Advance beginning of range by one.
+     *
+     * @pre Range must not be empty.
+     */
+    void drop_front() {
+        protozero_assert(!empty());
+        ++this->first;
+    }
+
+    /**
+     * Swap the contents of this range with the other.
+     *
+     * @param other Other range to swap data with.
+     */
+    void swap(iterator_range& other) noexcept {
+        using std::swap;
+        swap(this->first, other.first);
+        swap(this->second, other.second);
+    }
+
+}; // struct iterator_range
+
+/**
+ * Swap two iterator_ranges.
+ *
+ * @param lhs First range.
+ * @param rhs Second range.
+ */
+template <typename T>
+inline void swap(iterator_range<T>& lhs, iterator_range<T>& rhs) noexcept {
+    lhs.swap(rhs);
+}
+
+#ifdef PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED
+
+template <typename T>
+using const_fixed_iterator = const T*;
+
+/**
+ * Create iterator_range from char pointers to beginning and end of range.
+ *
+ * @param first Beginning of range.
+ * @param last End of range.
+ */
+template <typename T>
+inline iterator_range<const_fixed_iterator<T>> create_fixed_iterator_range(const char* first, const char* last) {
+    return iterator_range<const_fixed_iterator<T>>{reinterpret_cast<const T*>(first),
+                                                   reinterpret_cast<const T*>(last)};
+}
+
+#else
+
+/**
+ * A forward iterator used for accessing packed repeated fields of fixed
+ * length (fixed32, sfixed32, float, double).
+ */
+template <typename T>
+class const_fixed_iterator {
+
+    /// Pointer to current iterator position
+    const char* m_data;
+
+    /// Pointer to end iterator position
+    const char* m_end;
+
+public:
+
+    using iterator_category = std::forward_iterator_tag;
+    using value_type        = T;
+    using difference_type   = std::ptrdiff_t;
+    using pointer           = value_type*;
+    using reference         = value_type&;
+
+    const_fixed_iterator() noexcept :
+        m_data(nullptr),
+        m_end(nullptr) {
+    }
+
+    const_fixed_iterator(const char* data, const char* end) noexcept :
+        m_data(data),
+        m_end(end) {
+    }
+
+    const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
+    const_fixed_iterator(const_fixed_iterator&&) noexcept = default;
+
+    const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default;
+    const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default;
+
+    ~const_fixed_iterator() noexcept = default;
+
+    value_type operator*() const {
+        value_type result;
+        detail::copy_or_byteswap<sizeof(value_type)>(m_data , &result);
+        return result;
+    }
+
+    const_fixed_iterator& operator++() {
+        m_data += sizeof(value_type);
+        return *this;
+    }
+
+    const_fixed_iterator operator++(int) {
+        const const_fixed_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+    bool operator==(const const_fixed_iterator& rhs) const noexcept {
+        return m_data == rhs.m_data && m_end == rhs.m_end;
+    }
+
+    bool operator!=(const const_fixed_iterator& rhs) const noexcept {
+        return !(*this == rhs);
+    }
+
+}; // class const_fixed_iterator
+
+/**
+ * Create iterator_range from char pointers to beginning and end of range.
+ *
+ * @param first Beginning of range.
+ * @param last End of range.
+ */
+template <typename T>
+inline iterator_range<const_fixed_iterator<T>> create_fixed_iterator_range(const char* first, const char* last) {
+    return iterator_range<const_fixed_iterator<T>>{const_fixed_iterator<T>(first, last),
+                                                   const_fixed_iterator<T>(last, last)};
+}
+
+#endif
+
+/**
+ * A forward iterator used for accessing packed repeated varint fields
+ * (int32, uint32, int64, uint64, bool, enum).
+ */
+template <typename T>
+class const_varint_iterator {
+
+protected:
+
+    /// Pointer to current iterator position
+    const char* m_data;
+
+    /// Pointer to end iterator position
+    const char* m_end;
+
+public:
+
+    using iterator_category = std::forward_iterator_tag;
+    using value_type        = T;
+    using difference_type   = std::ptrdiff_t;
+    using pointer           = value_type*;
+    using reference         = value_type&;
+
+    const_varint_iterator() noexcept :
+        m_data(nullptr),
+        m_end(nullptr) {
+    }
+
+    const_varint_iterator(const char* data, const char* end) noexcept :
+        m_data(data),
+        m_end(end) {
+    }
+
+    const_varint_iterator(const const_varint_iterator&) noexcept = default;
+    const_varint_iterator(const_varint_iterator&&) noexcept = default;
+
+    const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default;
+    const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default;
+
+    ~const_varint_iterator() noexcept = default;
+
+    value_type operator*() const {
+        const char* d = m_data; // will be thrown away
+        return static_cast<value_type>(decode_varint(&d, m_end));
+    }
+
+    const_varint_iterator& operator++() {
+        skip_varint(&m_data, m_end);
+        return *this;
+    }
+
+    const_varint_iterator operator++(int) {
+        const const_varint_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+    bool operator==(const const_varint_iterator& rhs) const noexcept {
+        return m_data == rhs.m_data && m_end == rhs.m_end;
+    }
+
+    bool operator!=(const const_varint_iterator& rhs) const noexcept {
+        return !(*this == rhs);
+    }
+
+}; // class const_varint_iterator
+
+/**
+ * A forward iterator used for accessing packed repeated svarint fields
+ * (sint32, sint64).
+ */
+template <typename T>
+class const_svarint_iterator : public const_varint_iterator<T> {
+
+public:
+
+    using iterator_category = std::forward_iterator_tag;
+    using value_type        = T;
+    using difference_type   = std::ptrdiff_t;
+    using pointer           = value_type*;
+    using reference         = value_type&;
+
+    const_svarint_iterator() noexcept :
+        const_varint_iterator<T>() {
+    }
+
+    const_svarint_iterator(const char* data, const char* end) noexcept :
+        const_varint_iterator<T>(data, end) {
+    }
+
+    const_svarint_iterator(const const_svarint_iterator&) = default;
+    const_svarint_iterator(const_svarint_iterator&&) = default;
+
+    const_svarint_iterator& operator=(const const_svarint_iterator&) = default;
+    const_svarint_iterator& operator=(const_svarint_iterator&&) = default;
+
+    ~const_svarint_iterator() = default;
+
+    value_type operator*() const {
+        const char* d = this->m_data; // will be thrown away
+        return static_cast<value_type>(decode_zigzag64(decode_varint(&d, this->m_end)));
+    }
+
+    const_svarint_iterator& operator++() {
+        skip_varint(&this->m_data, this->m_end);
+        return *this;
+    }
+
+    const_svarint_iterator operator++(int) {
+        const const_svarint_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+}; // class const_svarint_iterator
+
+} // end namespace protozero
+
+#endif // PROTOZERO_ITERATORS_HPP
diff --git a/include/protozero/pbf_builder.hpp b/include/protozero/pbf_builder.hpp
index 548f4ce..39af53f 100644
--- a/include/protozero/pbf_builder.hpp
+++ b/include/protozero/pbf_builder.hpp
@@ -57,7 +57,7 @@ public:
 
 /// @cond INTERNAL
 #define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \
-    inline void add_##name(T tag, type value) { \
+    void add_##name(T tag, type value) { \
         pbf_writer::add_##name(pbf_tag_type(tag), value); \
     }
 
@@ -79,38 +79,38 @@ public:
 #undef PROTOZERO_WRITER_WRAP_ADD_SCALAR
 /// @endcond
 
-    inline void add_bytes(T tag, const char* value, std::size_t size) {
+    void add_bytes(T tag, const char* value, std::size_t size) {
         pbf_writer::add_bytes(pbf_tag_type(tag), value, size);
     }
 
-    inline void add_bytes(T tag, const std::string& value) {
+    void add_bytes(T tag, const std::string& value) {
         pbf_writer::add_bytes(pbf_tag_type(tag), value);
     }
 
-    inline void add_string(T tag, const char* value, std::size_t size) {
+    void add_string(T tag, const char* value, std::size_t size) {
         pbf_writer::add_string(pbf_tag_type(tag), value, size);
     }
 
-    inline void add_string(T tag, const std::string& value) {
+    void add_string(T tag, const std::string& value) {
         pbf_writer::add_string(pbf_tag_type(tag), value);
     }
 
-    inline void add_string(T tag, const char* value) {
+    void add_string(T tag, const char* value) {
         pbf_writer::add_string(pbf_tag_type(tag), value);
     }
 
-    inline void add_message(T tag, const char* value, std::size_t size) {
+    void add_message(T tag, const char* value, std::size_t size) {
         pbf_writer::add_message(pbf_tag_type(tag), value, size);
     }
 
-    inline void add_message(T tag, const std::string& value) {
+    void add_message(T tag, const std::string& value) {
         pbf_writer::add_message(pbf_tag_type(tag), value);
     }
 
 /// @cond INTERNAL
 #define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \
     template <typename InputIterator> \
-    inline void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
+    void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
         pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \
     }
 
@@ -132,7 +132,7 @@ public:
 #undef PROTOZERO_WRITER_WRAP_ADD_PACKED
 /// @endcond
 
-};
+}; // class pbf_builder
 
 } // end namespace protozero
 
diff --git a/include/protozero/pbf_message.hpp b/include/protozero/pbf_message.hpp
index 45f01c1..0557734 100644
--- a/include/protozero/pbf_message.hpp
+++ b/include/protozero/pbf_message.hpp
@@ -13,7 +13,7 @@ documentation.
 /**
  * @file pbf_message.hpp
  *
- * @brief Contains the pbf_message class.
+ * @brief Contains the pbf_message template class.
  */
 
 #include <type_traits>
@@ -75,19 +75,19 @@ public:
         pbf_reader(std::forward<Args>(args)...) {
     }
 
-    inline bool next() {
+    bool next() {
         return pbf_reader::next();
     }
 
-    inline bool next(T tag) {
+    bool next(T tag) {
         return pbf_reader::next(pbf_tag_type(tag));
     }
 
-    inline T tag() const noexcept {
+    T tag() const noexcept {
         return T(pbf_reader::tag());
     }
 
-};
+}; // class pbf_message
 
 } // end namespace protozero
 
diff --git a/include/protozero/pbf_reader.hpp b/include/protozero/pbf_reader.hpp
index 58b3884..2f4054d 100644
--- a/include/protozero/pbf_reader.hpp
+++ b/include/protozero/pbf_reader.hpp
@@ -18,13 +18,12 @@ documentation.
 
 #include <cstddef>
 #include <cstdint>
-#include <cstring>
-#include <iterator>
 #include <string>
 #include <utility>
 
 #include <protozero/config.hpp>
 #include <protozero/exception.hpp>
+#include <protozero/iterators.hpp>
 #include <protozero/types.hpp>
 #include <protozero/varint.hpp>
 
@@ -55,16 +54,16 @@ namespace protozero {
  *
  * All methods of the pbf_reader class except get_bytes() and get_string()
  * provide the strong exception guarantee, ie they either succeed or do not
- * change the pbf_reader object they are called on. Use the get_data() method
+ * change the pbf_reader object they are called on. Use the get_view() method
  * instead of get_bytes() or get_string(), if you need this guarantee.
  */
 class pbf_reader {
 
     // A pointer to the next unread data.
-    const char *m_data = nullptr;
+    const char* m_data = nullptr;
 
     // A pointer to one past the end of data.
-    const char *m_end = nullptr;
+    const char* m_end = nullptr;
 
     // The wire type of the current field.
     pbf_wire_type m_wire_type = pbf_wire_type::unknown;
@@ -72,119 +71,84 @@ class pbf_reader {
     // The tag of the current field.
     pbf_tag_type m_tag = 0;
 
-    // Copy N bytes from src to dest on little endian machines, on big endian
-    // swap the bytes in the process.
-    template <int N>
-    static void copy_or_byteswap(const char* src, void* dest) noexcept {
-#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
-        memcpy(dest, src, N);
-#else
-        byteswap<N>(src, reinterpret_cast<char*>(dest));
-#endif
-    }
-
     template <typename T>
-    inline T get_fixed() {
+    T get_fixed() {
         T result;
         skip_bytes(sizeof(T));
-        copy_or_byteswap<sizeof(T)>(m_data - sizeof(T), &result);
+        detail::copy_or_byteswap<sizeof(T)>(m_data - sizeof(T), &result);
         return result;
     }
 
-#ifdef PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED
-
     template <typename T>
-    using const_fixed_iterator = const T*;
+    iterator_range<const_fixed_iterator<T>> packed_fixed() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        const auto len = get_len_and_skip();
+        protozero_assert(len % sizeof(T) == 0);
+        return create_fixed_iterator_range<T>(m_data - len, m_data);
+    }
 
     template <typename T>
-    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> create_fixed_iterator_pair(const char* first, const char* last) {
-        return std::make_pair(reinterpret_cast<const T*>(first),
-                              reinterpret_cast<const T*>(last));
+    T get_varint() {
+        return static_cast<T>(decode_varint(&m_data, m_end));
     }
 
-#else
-
     template <typename T>
-    class const_fixed_iterator : public std::iterator<std::forward_iterator_tag, T> {
-
-        const char* m_data;
-        const char* m_end;
-
-    public:
-
-        const_fixed_iterator() noexcept :
-            m_data(nullptr),
-            m_end(nullptr) {
-        }
-
-        const_fixed_iterator(const char *data, const char* end) noexcept :
-            m_data(data),
-            m_end(end) {
-        }
-
-        const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
-        const_fixed_iterator(const_fixed_iterator&&) noexcept = default;
-
-        const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default;
-        const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default;
-
-        ~const_fixed_iterator() noexcept = default;
-
-        T operator*() {
-            T result;
-            copy_or_byteswap<sizeof(T)>(m_data , &result);
-            return result;
-        }
-
-        const_fixed_iterator& operator++() {
-            m_data += sizeof(T);
-            return *this;
-        }
-
-        const_fixed_iterator operator++(int) {
-            const const_fixed_iterator tmp(*this);
-            ++(*this);
-            return tmp;
-        }
+    T get_svarint() {
+        protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint");
+        return static_cast<T>(decode_zigzag64(decode_varint(&m_data, m_end)));
+    }
 
-        bool operator==(const const_fixed_iterator& rhs) const noexcept {
-            return m_data == rhs.m_data && m_end == rhs.m_end;
-        }
+    pbf_length_type get_length() {
+        return get_varint<pbf_length_type>();
+    }
 
-        bool operator!=(const const_fixed_iterator& rhs) const noexcept {
-            return !(*this == rhs);
+    void skip_bytes(pbf_length_type len) {
+        if (m_data + len > m_end) {
+            throw end_of_buffer_exception();
         }
+        m_data += len;
 
-    }; // class const_fixed_iterator
-
-    template <typename T>
-    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> create_fixed_iterator_pair(const char* first, const char* last) {
-        return std::make_pair(const_fixed_iterator<T>(first, last),
-                              const_fixed_iterator<T>(last, last));
+    // In debug builds reset the tag to zero so that we can detect (some)
+    // wrong code.
+#ifndef NDEBUG
+        m_tag = 0;
+#endif
     }
 
-#endif
+    pbf_length_type get_len_and_skip() {
+        const auto len = get_length();
+        skip_bytes(len);
+        return len;
+    }
 
     template <typename T>
-    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> packed_fixed() {
+    iterator_range<T> get_packed() {
         protozero_assert(tag() != 0 && "call next() before accessing field value");
-        auto len = get_len_and_skip();
-        protozero_assert(len % sizeof(T) == 0);
-        return create_fixed_iterator_pair<T>(m_data-len, m_data);
+        const auto len = get_len_and_skip();
+        return iterator_range<T>{T{m_data - len, m_data},
+                                 T{m_data, m_data}};
     }
 
-    template <typename T> inline T get_varint();
-    template <typename T> inline T get_svarint();
-
-    inline pbf_length_type get_length() { return get_varint<pbf_length_type>(); }
-
-    inline void skip_bytes(pbf_length_type len);
-
-    inline pbf_length_type get_len_and_skip();
-
 public:
 
     /**
+     * Construct a pbf_reader message from a data_view. The pointer from the
+     * data_view will be stored inside the pbf_reader object, no data is
+     * copied. So you must* make sure the view stays valid as long as the
+     * pbf_reader object is used.
+     *
+     * The buffer must contain a complete protobuf message.
+     *
+     * @post There is no current field.
+     */
+    explicit pbf_reader(const data_view& view) noexcept
+        : m_data(view.data()),
+          m_end(view.data() + view.size()),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
+
+    /**
      * Construct a pbf_reader message from a data pointer and a length. The pointer
      * will be stored inside the pbf_reader object, no data is copied. So you must
      * make sure the buffer stays valid as long as the pbf_reader object is used.
@@ -193,7 +157,12 @@ public:
      *
      * @post There is no current field.
      */
-    inline pbf_reader(const char *data, std::size_t length) noexcept;
+    pbf_reader(const char* data, std::size_t length) noexcept
+        : m_data(data),
+          m_end(data + length),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
 
     /**
      * Construct a pbf_reader message from a data pointer and a length. The pointer
@@ -204,7 +173,12 @@ public:
      *
      * @post There is no current field.
      */
-    inline pbf_reader(std::pair<const char *, std::size_t> data) noexcept;
+    pbf_reader(std::pair<const char*, std::size_t> data) noexcept
+        : m_data(data.first),
+          m_end(data.first + data.second),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
 
     /**
      * Construct a pbf_reader message from a std::string. A pointer to the string
@@ -216,33 +190,53 @@ public:
      *
      * @post There is no current field.
      */
-    inline pbf_reader(const std::string& data) noexcept;
+    pbf_reader(const std::string& data) noexcept
+        : m_data(data.data()),
+          m_end(data.data() + data.size()),
+          m_wire_type(pbf_wire_type::unknown),
+          m_tag(0) {
+    }
 
     /**
      * pbf_reader can be default constructed and behaves like it has an empty
      * buffer.
      */
-    inline pbf_reader() noexcept = default;
+    pbf_reader() noexcept = default;
 
     /// pbf_reader messages can be copied trivially.
-    inline pbf_reader(const pbf_reader&) noexcept = default;
+    pbf_reader(const pbf_reader&) noexcept = default;
 
     /// pbf_reader messages can be moved trivially.
-    inline pbf_reader(pbf_reader&&) noexcept = default;
+    pbf_reader(pbf_reader&&) noexcept = default;
 
     /// pbf_reader messages can be copied trivially.
-    inline pbf_reader& operator=(const pbf_reader& other) noexcept = default;
+    pbf_reader& operator=(const pbf_reader& other) noexcept = default;
 
     /// pbf_reader messages can be moved trivially.
-    inline pbf_reader& operator=(pbf_reader&& other) noexcept = default;
+    pbf_reader& operator=(pbf_reader&& other) noexcept = default;
+
+    ~pbf_reader() = default;
 
-    inline ~pbf_reader() = default;
+    /**
+     * Swap the contents of this object with the other.
+     *
+     * @param other Other object to swap data with.
+     */
+    void swap(pbf_reader& other) noexcept {
+        using std::swap;
+        swap(m_data, other.m_data);
+        swap(m_end, other.m_end);
+        swap(m_wire_type, other.m_wire_type);
+        swap(m_tag, other.m_tag);
+    }
 
     /**
      * In a boolean context the pbf_reader class evaluates to `true` if there are
      * still fields available and to `false` if the last field has been read.
      */
-    inline operator bool() const noexcept;
+    operator bool() const noexcept {
+        return m_data < m_end;
+    }
 
     /**
      * Return the length in bytes of the current message. If you have
@@ -272,7 +266,31 @@ public:
      * @pre There must be no current field.
      * @post If it returns `true` there is a current field now.
      */
-    inline bool next();
+    bool next() {
+        if (m_data == m_end) {
+            return false;
+        }
+
+        const auto value = get_varint<uint32_t>();
+        m_tag = pbf_tag_type(value >> 3);
+
+        // tags 0 and 19000 to 19999 are not allowed as per
+        // https://developers.google.com/protocol-buffers/docs/proto
+        protozero_assert(((m_tag > 0 && m_tag < 19000) || (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range");
+
+        m_wire_type = pbf_wire_type(value & 0x07);
+        switch (m_wire_type) {
+            case pbf_wire_type::varint:
+            case pbf_wire_type::fixed64:
+            case pbf_wire_type::length_delimited:
+            case pbf_wire_type::fixed32:
+                break;
+            default:
+                throw unknown_pbf_wire_type_exception();
+        }
+
+        return true;
+    }
 
     /**
      * Set next field with given tag in the message as the current field.
@@ -299,7 +317,16 @@ public:
      * @pre There must be no current field.
      * @post If it returns `true` there is a current field now with the given tag.
      */
-    inline bool next(pbf_tag_type tag);
+    bool next(pbf_tag_type tag) {
+        while (next()) {
+            if (m_tag == tag) {
+                return true;
+            } else {
+                skip();
+            }
+        }
+        return false;
+    }
 
     /**
      * The tag of the current field. The tag is the field number from the
@@ -310,7 +337,9 @@ public:
      * @returns tag of the current field.
      * @pre There must be a current field (ie. next() must have returned `true`).
      */
-    inline pbf_tag_type tag() const noexcept;
+    pbf_tag_type tag() const noexcept {
+        return m_tag;
+    }
 
     /**
      * Get the wire type of the current field. The wire types are:
@@ -327,7 +356,9 @@ public:
      * @returns wire type of the current field.
      * @pre There must be a current field (ie. next() must have returned `true`).
      */
-    inline pbf_wire_type wire_type() const noexcept;
+    pbf_wire_type wire_type() const noexcept {
+        return m_wire_type;
+    }
 
     /**
      * Check the wire type of the current field.
@@ -335,7 +366,9 @@ public:
      * @returns `true` if the current field has the given wire type.
      * @pre There must be a current field (ie. next() must have returned `true`).
      */
-    inline bool has_wire_type(pbf_wire_type type) const noexcept;
+    bool has_wire_type(pbf_wire_type type) const noexcept {
+        return wire_type() == type;
+    }
 
     /**
      * Consume the current field.
@@ -343,7 +376,25 @@ public:
      * @pre There must be a current field (ie. next() must have returned `true`).
      * @post The current field was consumed and there is no current field now.
      */
-    inline void skip();
+    void skip() {
+        protozero_assert(tag() != 0 && "call next() before calling skip()");
+        switch (wire_type()) {
+            case pbf_wire_type::varint:
+                skip_varint(&m_data, m_end);
+                break;
+            case pbf_wire_type::fixed64:
+                skip_bytes(8);
+                break;
+            case pbf_wire_type::length_delimited:
+                skip_bytes(get_length());
+                break;
+            case pbf_wire_type::fixed32:
+                skip_bytes(4);
+                break;
+            default:
+                protozero_assert(false && "can not be here because next() should have thrown already");
+        }
+    }
 
     ///@{
     /**
@@ -357,7 +408,13 @@ public:
      * @pre The current field must be of type "bool".
      * @post The current field was consumed and there is no current field now.
      */
-    inline bool get_bool();
+    bool get_bool() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint");
+        skip_bytes(1);
+        return m_data[-1] != 0; // -1 okay because we incremented m_data the line before
+    }
 
     /**
      * Consume and return value of current "enum" field.
@@ -366,7 +423,7 @@ public:
      * @pre The current field must be of type "enum".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_enum() {
+    int32_t get_enum() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<int32_t>();
     }
@@ -378,7 +435,7 @@ public:
      * @pre The current field must be of type "int32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_int32() {
+    int32_t get_int32() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<int32_t>();
     }
@@ -390,7 +447,7 @@ public:
      * @pre The current field must be of type "sint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_sint32() {
+    int32_t get_sint32() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_svarint<int32_t>();
     }
@@ -402,7 +459,7 @@ public:
      * @pre The current field must be of type "uint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint32_t get_uint32() {
+    uint32_t get_uint32() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<uint32_t>();
     }
@@ -414,7 +471,7 @@ public:
      * @pre The current field must be of type "int64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int64_t get_int64() {
+    int64_t get_int64() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<int64_t>();
     }
@@ -426,7 +483,7 @@ public:
      * @pre The current field must be of type "sint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int64_t get_sint64() {
+    int64_t get_sint64() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_svarint<int64_t>();
     }
@@ -438,7 +495,7 @@ public:
      * @pre The current field must be of type "uint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint64_t get_uint64() {
+    uint64_t get_uint64() {
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
         return get_varint<uint64_t>();
     }
@@ -450,7 +507,11 @@ public:
      * @pre The current field must be of type "fixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint32_t get_fixed32();
+    uint32_t get_fixed32() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+        return get_fixed<uint32_t>();
+    }
 
     /**
      * Consume and return value of current "sfixed32" field.
@@ -459,7 +520,11 @@ public:
      * @pre The current field must be of type "sfixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int32_t get_sfixed32();
+    int32_t get_sfixed32() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+        return get_fixed<int32_t>();
+    }
 
     /**
      * Consume and return value of current "fixed64" field.
@@ -468,7 +533,11 @@ public:
      * @pre The current field must be of type "fixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline uint64_t get_fixed64();
+    uint64_t get_fixed64() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+        return get_fixed<uint64_t>();
+    }
 
     /**
      * Consume and return value of current "sfixed64" field.
@@ -477,7 +546,11 @@ public:
      * @pre The current field must be of type "sfixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline int64_t get_sfixed64();
+    int64_t get_sfixed64() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+        return get_fixed<int64_t>();
+    }
 
     /**
      * Consume and return value of current "float" field.
@@ -486,7 +559,11 @@ public:
      * @pre The current field must be of type "float".
      * @post The current field was consumed and there is no current field now.
      */
-    inline float get_float();
+    float get_float() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+        return get_fixed<float>();
+    }
 
     /**
      * Consume and return value of current "double" field.
@@ -495,8 +572,29 @@ public:
      * @pre The current field must be of type "double".
      * @post The current field was consumed and there is no current field now.
      */
-    inline double get_double();
+    double get_double() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+        return get_fixed<double>();
+    }
+
+    /**
+     * Consume and return value of current "bytes", "string", or "message"
+     * field.
+     *
+     * @returns A data_view object.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "bytes", "string", or "message".
+     * @post The current field was consumed and there is no current field now.
+     */
+    data_view get_view() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
+        const auto len = get_len_and_skip();
+        return data_view{m_data-len, len};
+    }
 
+#ifndef PROTOZERO_STRICT_API
     /**
      * Consume and return value of current "bytes" or "string" field.
      *
@@ -505,7 +603,13 @@ public:
      * @pre The current field must be of type "bytes" or "string".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<const char*, pbf_length_type> get_data();
+    std::pair<const char*, pbf_length_type> get_data() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
+        const auto len = get_len_and_skip();
+        return std::make_pair(m_data-len, len);
+    }
+#endif
 
     /**
      * Consume and return value of current "bytes" field.
@@ -514,7 +618,9 @@ public:
      * @pre The current field must be of type "bytes".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::string get_bytes();
+    std::string get_bytes() {
+        return std::string(get_view());
+    }
 
     /**
      * Consume and return value of current "string" field.
@@ -523,7 +629,9 @@ public:
      * @pre The current field must be of type "string".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::string get_string();
+    std::string get_string() {
+        return std::string(get_view());
+    }
 
     /**
      * Consume and return value of current "message" field.
@@ -532,136 +640,35 @@ public:
      * @pre The current field must be of type "message".
      * @post The current field was consumed and there is no current field now.
      */
-    inline pbf_reader get_message() {
-        return pbf_reader(get_data());
+    pbf_reader get_message() {
+        return pbf_reader(get_view());
     }
 
     ///@}
 
-private:
-
-    template <typename T>
-    class const_varint_iterator : public std::iterator<std::forward_iterator_tag, T> {
-
-    protected:
-
-        const char* m_data;
-        const char* m_end;
-
-    public:
-
-        const_varint_iterator() noexcept :
-            m_data(nullptr),
-            m_end(nullptr) {
-        }
-
-        const_varint_iterator(const char *data, const char* end) noexcept :
-            m_data(data),
-            m_end(end) {
-        }
-
-        const_varint_iterator(const const_varint_iterator&) noexcept = default;
-        const_varint_iterator(const_varint_iterator&&) noexcept = default;
-
-        const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default;
-        const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default;
-
-        ~const_varint_iterator() noexcept = default;
-
-        T operator*() {
-            const char* d = m_data; // will be thrown away
-            return static_cast<T>(decode_varint(&d, m_end));
-        }
-
-        const_varint_iterator& operator++() {
-            // Ignore the result, we call decode_varint() just for the
-            // side-effect of updating m_data.
-            decode_varint(&m_data, m_end);
-            return *this;
-        }
-
-        const_varint_iterator operator++(int) {
-            const const_varint_iterator tmp(*this);
-            ++(*this);
-            return tmp;
-        }
-
-        bool operator==(const const_varint_iterator& rhs) const noexcept {
-            return m_data == rhs.m_data && m_end == rhs.m_end;
-        }
-
-        bool operator!=(const const_varint_iterator& rhs) const noexcept {
-            return !(*this == rhs);
-        }
-
-    }; // class const_varint_iterator
-
-    template <typename T>
-    class const_svarint_iterator : public const_varint_iterator<T> {
-
-    public:
-
-        const_svarint_iterator() noexcept :
-            const_varint_iterator<T>() {
-        }
-
-        const_svarint_iterator(const char *data, const char* end) noexcept :
-            const_varint_iterator<T>(data, end) {
-        }
-
-        const_svarint_iterator(const const_svarint_iterator&) = default;
-        const_svarint_iterator(const_svarint_iterator&&) = default;
-
-        const_svarint_iterator& operator=(const const_svarint_iterator&) = default;
-        const_svarint_iterator& operator=(const_svarint_iterator&&) = default;
-
-        ~const_svarint_iterator() = default;
-
-        T operator*() {
-            const char* d = this->m_data; // will be thrown away
-            return static_cast<T>(decode_zigzag64(decode_varint(&d, this->m_end)));
-        }
-
-        const_svarint_iterator& operator++() {
-            // Ignore the result, we call decode_varint() just for the
-            // side-effect of updating m_data.
-            decode_varint(&this->m_data, this->m_end);
-            return *this;
-        }
-
-        const_svarint_iterator operator++(int) {
-            const const_svarint_iterator tmp(*this);
-            ++(*this);
-            return tmp;
-        }
-
-    }; // class const_svarint_iterator
-
-public:
-
     /// Forward iterator for iterating over bool (int32 varint) values.
-    typedef const_varint_iterator< int32_t> const_bool_iterator;
+    using const_bool_iterator   = const_varint_iterator< int32_t>;
 
     /// Forward iterator for iterating over enum (int32 varint) values.
-    typedef const_varint_iterator< int32_t> const_enum_iterator;
+    using const_enum_iterator   = const_varint_iterator< int32_t>;
 
     /// Forward iterator for iterating over int32 (varint) values.
-    typedef const_varint_iterator< int32_t> const_int32_iterator;
+    using const_int32_iterator  = const_varint_iterator< int32_t>;
 
     /// Forward iterator for iterating over sint32 (varint) values.
-    typedef const_svarint_iterator<int32_t> const_sint32_iterator;
+    using const_sint32_iterator = const_svarint_iterator<int32_t>;
 
     /// Forward iterator for iterating over uint32 (varint) values.
-    typedef const_varint_iterator<uint32_t> const_uint32_iterator;
+    using const_uint32_iterator = const_varint_iterator<uint32_t>;
 
     /// Forward iterator for iterating over int64 (varint) values.
-    typedef const_varint_iterator< int64_t> const_int64_iterator;
+    using const_int64_iterator  = const_varint_iterator< int64_t>;
 
     /// Forward iterator for iterating over sint64 (varint) values.
-    typedef const_svarint_iterator<int64_t> const_sint64_iterator;
+    using const_sint64_iterator = const_svarint_iterator<int64_t>;
 
     /// Forward iterator for iterating over uint64 (varint) values.
-    typedef const_varint_iterator<uint64_t> const_uint64_iterator;
+    using const_uint64_iterator = const_varint_iterator<uint64_t>;
 
     ///@{
     /**
@@ -677,7 +684,9 @@ public:
      * @pre The current field must be of type "repeated packed bool".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_bool_iterator, pbf_reader::const_bool_iterator> get_packed_bool();
+    iterator_range<pbf_reader::const_bool_iterator> get_packed_bool() {
+        return get_packed<pbf_reader::const_bool_iterator>();
+    }
 
     /**
      * Consume current "repeated packed enum" field.
@@ -688,7 +697,9 @@ public:
      * @pre The current field must be of type "repeated packed enum".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_enum_iterator, pbf_reader::const_enum_iterator> get_packed_enum();
+    iterator_range<pbf_reader::const_enum_iterator> get_packed_enum() {
+        return get_packed<pbf_reader::const_enum_iterator>();
+    }
 
     /**
      * Consume current "repeated packed int32" field.
@@ -699,7 +710,9 @@ public:
      * @pre The current field must be of type "repeated packed int32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_int32_iterator, pbf_reader::const_int32_iterator> get_packed_int32();
+    iterator_range<pbf_reader::const_int32_iterator> get_packed_int32() {
+        return get_packed<pbf_reader::const_int32_iterator>();
+    }
 
     /**
      * Consume current "repeated packed sint32" field.
@@ -710,7 +723,9 @@ public:
      * @pre The current field must be of type "repeated packed sint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_sint32_iterator, pbf_reader::const_sint32_iterator> get_packed_sint32();
+    iterator_range<pbf_reader::const_sint32_iterator> get_packed_sint32() {
+        return get_packed<pbf_reader::const_sint32_iterator>();
+    }
 
     /**
      * Consume current "repeated packed uint32" field.
@@ -721,7 +736,9 @@ public:
      * @pre The current field must be of type "repeated packed uint32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_uint32_iterator, pbf_reader::const_uint32_iterator> get_packed_uint32();
+    iterator_range<pbf_reader::const_uint32_iterator> get_packed_uint32() {
+        return get_packed<pbf_reader::const_uint32_iterator>();
+    }
 
     /**
      * Consume current "repeated packed int64" field.
@@ -732,7 +749,9 @@ public:
      * @pre The current field must be of type "repeated packed int64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_int64_iterator, pbf_reader::const_int64_iterator> get_packed_int64();
+    iterator_range<pbf_reader::const_int64_iterator> get_packed_int64() {
+        return get_packed<pbf_reader::const_int64_iterator>();
+    }
 
     /**
      * Consume current "repeated packed sint64" field.
@@ -743,7 +762,9 @@ public:
      * @pre The current field must be of type "repeated packed sint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_sint64_iterator, pbf_reader::const_sint64_iterator> get_packed_sint64();
+    iterator_range<pbf_reader::const_sint64_iterator> get_packed_sint64() {
+        return get_packed<pbf_reader::const_sint64_iterator>();
+    }
 
     /**
      * Consume current "repeated packed uint64" field.
@@ -754,7 +775,9 @@ public:
      * @pre The current field must be of type "repeated packed uint64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline std::pair<pbf_reader::const_uint64_iterator, pbf_reader::const_uint64_iterator> get_packed_uint64();
+    iterator_range<pbf_reader::const_uint64_iterator> get_packed_uint64() {
+        return get_packed<pbf_reader::const_uint64_iterator>();
+    }
 
     /**
      * Consume current "repeated packed fixed32" field.
@@ -765,7 +788,7 @@ public:
      * @pre The current field must be of type "repeated packed fixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_fixed32() -> decltype(packed_fixed<uint32_t>()) {
+    auto get_packed_fixed32() -> decltype(packed_fixed<uint32_t>()) {
         return packed_fixed<uint32_t>();
     }
 
@@ -778,7 +801,7 @@ public:
      * @pre The current field must be of type "repeated packed sfixed32".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_sfixed32() -> decltype(packed_fixed<int32_t>()) {
+    auto get_packed_sfixed32() -> decltype(packed_fixed<int32_t>()) {
         return packed_fixed<int32_t>();
     }
 
@@ -791,7 +814,7 @@ public:
      * @pre The current field must be of type "repeated packed fixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_fixed64() -> decltype(packed_fixed<uint64_t>()) {
+    auto get_packed_fixed64() -> decltype(packed_fixed<uint64_t>()) {
         return packed_fixed<uint64_t>();
     }
 
@@ -804,7 +827,7 @@ public:
      * @pre The current field must be of type "repeated packed sfixed64".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_sfixed64() -> decltype(packed_fixed<int64_t>()) {
+    auto get_packed_sfixed64() -> decltype(packed_fixed<int64_t>()) {
         return packed_fixed<int64_t>();
     }
 
@@ -817,7 +840,7 @@ public:
      * @pre The current field must be of type "repeated packed float".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_float() -> decltype(packed_fixed<float>()) {
+    auto get_packed_float() -> decltype(packed_fixed<float>()) {
         return packed_fixed<float>();
     }
 
@@ -830,7 +853,7 @@ public:
      * @pre The current field must be of type "repeated packed double".
      * @post The current field was consumed and there is no current field now.
      */
-    inline auto get_packed_double() -> decltype(packed_fixed<double>()) {
+    auto get_packed_double() -> decltype(packed_fixed<double>()) {
         return packed_fixed<double>();
     }
 
@@ -838,238 +861,14 @@ public:
 
 }; // class pbf_reader
 
-pbf_reader::pbf_reader(const char *data, std::size_t length) noexcept
-    : m_data(data),
-      m_end(data + length),
-      m_wire_type(pbf_wire_type::unknown),
-      m_tag(0) {
-}
-
-pbf_reader::pbf_reader(std::pair<const char *, std::size_t> data) noexcept
-    : m_data(data.first),
-      m_end(data.first + data.second),
-      m_wire_type(pbf_wire_type::unknown),
-      m_tag(0) {
-}
-
-pbf_reader::pbf_reader(const std::string& data) noexcept
-    : m_data(data.data()),
-      m_end(data.data() + data.size()),
-      m_wire_type(pbf_wire_type::unknown),
-      m_tag(0) {
-}
-
-pbf_reader::operator bool() const noexcept {
-    return m_data < m_end;
-}
-
-bool pbf_reader::next() {
-    if (m_data == m_end) {
-        return false;
-    }
-
-    auto value = get_varint<uint32_t>();
-    m_tag = value >> 3;
-
-    // tags 0 and 19000 to 19999 are not allowed as per
-    // https://developers.google.com/protocol-buffers/docs/proto
-    protozero_assert(((m_tag > 0 && m_tag < 19000) || (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range");
-
-    m_wire_type = pbf_wire_type(value & 0x07);
-    switch (m_wire_type) {
-        case pbf_wire_type::varint:
-        case pbf_wire_type::fixed64:
-        case pbf_wire_type::length_delimited:
-        case pbf_wire_type::fixed32:
-            break;
-        default:
-            throw unknown_pbf_wire_type_exception();
-    }
-
-    return true;
-}
-
-bool pbf_reader::next(pbf_tag_type requested_tag) {
-    while (next()) {
-        if (m_tag == requested_tag) {
-            return true;
-        } else {
-            skip();
-        }
-    }
-    return false;
-}
-
-pbf_tag_type pbf_reader::tag() const noexcept {
-    return m_tag;
-}
-
-pbf_wire_type pbf_reader::wire_type() const noexcept {
-    return m_wire_type;
-}
-
-bool pbf_reader::has_wire_type(pbf_wire_type type) const noexcept {
-    return wire_type() == type;
-}
-
-void pbf_reader::skip_bytes(pbf_length_type len) {
-    if (m_data + len > m_end) {
-        throw end_of_buffer_exception();
-    }
-    m_data += len;
-
-// In debug builds reset the tag to zero so that we can detect (some)
-// wrong code.
-#ifndef NDEBUG
-    m_tag = 0;
-#endif
-}
-
-void pbf_reader::skip() {
-    protozero_assert(tag() != 0 && "call next() before calling skip()");
-    switch (wire_type()) {
-        case pbf_wire_type::varint:
-            (void)get_uint32(); // called for the side-effect of skipping value
-            break;
-        case pbf_wire_type::fixed64:
-            skip_bytes(8);
-            break;
-        case pbf_wire_type::length_delimited:
-            skip_bytes(get_length());
-            break;
-        case pbf_wire_type::fixed32:
-            skip_bytes(4);
-            break;
-        default:
-            protozero_assert(false && "can not be here because next() should have thrown already");
-    }
-}
-
-pbf_length_type pbf_reader::get_len_and_skip() {
-    auto len = get_length();
-    skip_bytes(len);
-    return len;
-}
-
-template <typename T>
-T pbf_reader::get_varint() {
-    return static_cast<T>(decode_varint(&m_data, m_end));
-}
-
-template <typename T>
-T pbf_reader::get_svarint() {
-    protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint");
-    return static_cast<T>(decode_zigzag64(decode_varint(&m_data, m_end)));
-}
-
-uint32_t pbf_reader::get_fixed32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
-    return get_fixed<uint32_t>();
-}
-
-int32_t pbf_reader::get_sfixed32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
-    return get_fixed<int32_t>();
-}
-
-uint64_t pbf_reader::get_fixed64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
-    return get_fixed<uint64_t>();
-}
-
-int64_t pbf_reader::get_sfixed64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
-    return get_fixed<int64_t>();
-}
-
-float pbf_reader::get_float() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
-    return get_fixed<float>();
-}
-
-double pbf_reader::get_double() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
-    return get_fixed<double>();
-}
-
-bool pbf_reader::get_bool() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
-    protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint");
-    skip_bytes(1);
-    return m_data[-1] != 0; // -1 okay because we incremented m_data the line before
-}
-
-std::pair<const char*, pbf_length_type> pbf_reader::get_data() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
-    auto len = get_len_and_skip();
-    return std::make_pair(m_data-len, len);
-}
-
-std::string pbf_reader::get_bytes() {
-    auto d = get_data();
-    return std::string(d.first, d.second);
-}
-
-std::string pbf_reader::get_string() {
-    return get_bytes();
-}
-
-std::pair<pbf_reader::const_bool_iterator, pbf_reader::const_bool_iterator> pbf_reader::get_packed_bool() {
-    return get_packed_int32();
-}
-
-std::pair<pbf_reader::const_enum_iterator, pbf_reader::const_enum_iterator> pbf_reader::get_packed_enum() {
-    return get_packed_int32();
-}
-
-std::pair<pbf_reader::const_int32_iterator, pbf_reader::const_int32_iterator> pbf_reader::get_packed_int32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_int32_iterator(m_data-len, m_data),
-                          pbf_reader::const_int32_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_uint32_iterator, pbf_reader::const_uint32_iterator> pbf_reader::get_packed_uint32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_uint32_iterator(m_data-len, m_data),
-                          pbf_reader::const_uint32_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_sint32_iterator, pbf_reader::const_sint32_iterator> pbf_reader::get_packed_sint32() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_sint32_iterator(m_data-len, m_data),
-                          pbf_reader::const_sint32_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_int64_iterator, pbf_reader::const_int64_iterator> pbf_reader::get_packed_int64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_int64_iterator(m_data-len, m_data),
-                          pbf_reader::const_int64_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_uint64_iterator, pbf_reader::const_uint64_iterator> pbf_reader::get_packed_uint64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_uint64_iterator(m_data-len, m_data),
-                          pbf_reader::const_uint64_iterator(m_data, m_data));
-}
-
-std::pair<pbf_reader::const_sint64_iterator, pbf_reader::const_sint64_iterator> pbf_reader::get_packed_sint64() {
-    protozero_assert(tag() != 0 && "call next() before accessing field value");
-    auto len = get_len_and_skip();
-    return std::make_pair(pbf_reader::const_sint64_iterator(m_data-len, m_data),
-                          pbf_reader::const_sint64_iterator(m_data, m_data));
+/**
+ * Swap two pbf_reader objects.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline void swap(pbf_reader& lhs, pbf_reader& rhs) noexcept {
+    lhs.swap(rhs);
 }
 
 } // end namespace protozero
diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp
index 422e147..3ce0f14 100644
--- a/include/protozero/pbf_writer.hpp
+++ b/include/protozero/pbf_writer.hpp
@@ -22,6 +22,7 @@ documentation.
 #include <iterator>
 #include <limits>
 #include <string>
+#include <utility>
 
 #include <protozero/config.hpp>
 #include <protozero/types.hpp>
@@ -68,38 +69,38 @@ class pbf_writer {
     // parent to the position where the data of the submessage is written to.
     std::size_t m_pos = 0;
 
-    inline void add_varint(uint64_t value) {
+    void add_varint(uint64_t value) {
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
         write_varint(std::back_inserter(*m_data), value);
     }
 
-    inline void add_field(pbf_tag_type tag, pbf_wire_type type) {
+    void add_field(pbf_tag_type tag, pbf_wire_type type) {
         protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1 << 29) - 1))) && "tag out of range");
-        uint32_t b = (tag << 3) | uint32_t(type);
+        const uint32_t b = (tag << 3) | uint32_t(type);
         add_varint(b);
     }
 
-    inline void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
+    void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
         add_field(tag, pbf_wire_type::varint);
         add_varint(value);
     }
 
     template <typename T>
-    inline void add_fixed(T value) {
+    void add_fixed(T value) {
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
 #if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
         m_data->append(reinterpret_cast<const char*>(&value), sizeof(T));
 #else
-        auto size = m_data->size();
+        const auto size = m_data->size();
         m_data->resize(size + sizeof(T));
         byteswap<sizeof(T)>(reinterpret_cast<const char*>(&value), const_cast<char*>(m_data->data() + size));
 #endif
     }
 
     template <typename T, typename It>
-    inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
+    void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
         if (first == last) {
             return;
         }
@@ -112,12 +113,12 @@ class pbf_writer {
     }
 
     template <typename T, typename It>
-    inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
+    void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
         if (first == last) {
             return;
         }
 
-        auto length = std::distance(first, last);
+        const auto length = std::distance(first, last);
         add_length_varint(tag, sizeof(T) * pbf_length_type(length));
         reserve(sizeof(T) * std::size_t(length));
 
@@ -127,7 +128,7 @@ class pbf_writer {
     }
 
     template <typename It>
-    inline void add_packed_varint(pbf_tag_type tag, It first, It last) {
+    void add_packed_varint(pbf_tag_type tag, It first, It last) {
         if (first == last) {
             return;
         }
@@ -140,7 +141,7 @@ class pbf_writer {
     }
 
     template <typename It>
-    inline void add_packed_svarint(pbf_tag_type tag, It first, It last) {
+    void add_packed_svarint(pbf_tag_type tag, It first, It last) {
         if (first == last) {
             return;
         }
@@ -155,14 +156,14 @@ class pbf_writer {
     // The number of bytes to reserve for the varint holding the length of
     // a length-delimited field. The length has to fit into pbf_length_type,
     // and a varint needs 8 bit for every 7 bit.
-    static const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1;
+    static constexpr const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1;
 
     // If m_rollpack_pos is set to this special value, it means that when
     // the submessage is closed, nothing needs to be done, because the length
     // of the submessage has already been written correctly.
-    static const std::size_t size_is_known = std::numeric_limits<std::size_t>::max();
+    static constexpr const std::size_t size_is_known = std::numeric_limits<std::size_t>::max();
 
-    inline void open_submessage(pbf_tag_type tag, std::size_t size) {
+    void open_submessage(pbf_tag_type tag, std::size_t size) {
         protozero_assert(m_pos == 0);
         protozero_assert(m_data);
         if (size == 0) {
@@ -177,7 +178,7 @@ class pbf_writer {
         m_pos = m_data->size();
     }
 
-    inline void rollback_submessage() {
+    void rollback_submessage() {
         protozero_assert(m_pos != 0);
         protozero_assert(m_rollback_pos != size_is_known);
         protozero_assert(m_data);
@@ -185,20 +186,20 @@ class pbf_writer {
         m_pos = 0;
     }
 
-    inline void commit_submessage() {
+    void commit_submessage() {
         protozero_assert(m_pos != 0);
         protozero_assert(m_rollback_pos != size_is_known);
         protozero_assert(m_data);
-        auto length = pbf_length_type(m_data->size() - m_pos);
+        const auto length = pbf_length_type(m_data->size() - m_pos);
 
         protozero_assert(m_data->size() >= m_pos - reserve_bytes);
-        auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
+        const auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
 
         m_data->erase(m_data->begin() + long(m_pos) - reserve_bytes + n, m_data->begin() + long(m_pos));
         m_pos = 0;
     }
 
-    inline void close_submessage() {
+    void close_submessage() {
         protozero_assert(m_data);
         if (m_pos == 0 || m_rollback_pos == size_is_known) {
             return;
@@ -210,7 +211,7 @@ class pbf_writer {
         }
     }
 
-    inline void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
+    void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
         add_field(tag, pbf_wire_type::length_delimited);
         add_varint(length);
     }
@@ -222,7 +223,7 @@ public:
      * stores a reference to that string and adds all data to it. The string
      * doesn't have to be empty. The pbf_writer will just append data.
      */
-    inline explicit pbf_writer(std::string& data) noexcept :
+    explicit pbf_writer(std::string& data) noexcept :
         m_data(&data),
         m_parent_writer(nullptr),
         m_pos(0) {
@@ -232,7 +233,7 @@ public:
      * Create a writer without a data store. In this form the writer can not
      * be used!
      */
-    inline pbf_writer() noexcept :
+    pbf_writer() noexcept :
         m_data(nullptr),
         m_parent_writer(nullptr),
         m_pos(0) {
@@ -248,7 +249,7 @@ public:
      *        Setting this allows some optimizations but is only possible in
      *        a few very specific cases.
      */
-    inline pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) :
+    pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) :
         m_data(parent_writer.m_data),
         m_parent_writer(&parent_writer),
         m_pos(0) {
@@ -262,18 +263,31 @@ public:
     pbf_writer& operator=(const pbf_writer&) noexcept = default;
 
     /// A pbf_writer object can be moved
-    inline pbf_writer(pbf_writer&&) noexcept = default;
+    pbf_writer(pbf_writer&&) noexcept = default;
 
     /// A pbf_writer object can be moved
-    inline pbf_writer& operator=(pbf_writer&&) noexcept = default;
+    pbf_writer& operator=(pbf_writer&&) noexcept = default;
 
-    inline ~pbf_writer() {
+    ~pbf_writer() {
         if (m_parent_writer) {
             m_parent_writer->close_submessage();
         }
     }
 
     /**
+     * Swap the contents of this object with the other.
+     *
+     * @param other Other object to swap data with.
+     */
+    void swap(pbf_writer& other) noexcept {
+        using std::swap;
+        swap(m_data, other.m_data);
+        swap(m_parent_writer, other.m_parent_writer);
+        swap(m_rollback_pos, other.m_rollback_pos);
+        swap(m_pos, other.m_pos);
+    }
+
+    /**
      * Reserve size bytes in the underlying message store in addition to
      * whatever the message store already holds. So unlike
      * the `std::string::reserve()` method this is not an absolute size,
@@ -286,7 +300,14 @@ public:
         m_data->reserve(m_data->size() + size);
     }
 
-    inline void rollback() {
+    /**
+     * Cancel writing of this submessage. The complete submessage will be
+     * removed as if it was never created and no fields were added.
+     *
+     * @pre Must be a pbf_writer of a submessage, ie one opened with the
+     *      pbf_writer constructor taking a parent message.
+     */
+    void rollback() {
         protozero_assert(m_parent_writer && "you can't call rollback() on a pbf_writer without a parent");
         protozero_assert(m_pos == 0 && "you can't call rollback() on a pbf_writer that has an open nested submessage");
         m_parent_writer->rollback_submessage();
@@ -304,7 +325,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_bool(pbf_tag_type tag, bool value) {
+    void add_bool(pbf_tag_type tag, bool value) {
         add_field(tag, pbf_wire_type::varint);
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
@@ -317,7 +338,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_enum(pbf_tag_type tag, int32_t value) {
+    void add_enum(pbf_tag_type tag, int32_t value) {
         add_tagged_varint(tag, uint64_t(value));
     }
 
@@ -327,7 +348,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_int32(pbf_tag_type tag, int32_t value) {
+    void add_int32(pbf_tag_type tag, int32_t value) {
         add_tagged_varint(tag, uint64_t(value));
     }
 
@@ -337,7 +358,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sint32(pbf_tag_type tag, int32_t value) {
+    void add_sint32(pbf_tag_type tag, int32_t value) {
         add_tagged_varint(tag, encode_zigzag32(value));
     }
 
@@ -347,7 +368,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_uint32(pbf_tag_type tag, uint32_t value) {
+    void add_uint32(pbf_tag_type tag, uint32_t value) {
         add_tagged_varint(tag, value);
     }
 
@@ -357,7 +378,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_int64(pbf_tag_type tag, int64_t value) {
+    void add_int64(pbf_tag_type tag, int64_t value) {
         add_tagged_varint(tag, uint64_t(value));
     }
 
@@ -367,7 +388,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sint64(pbf_tag_type tag, int64_t value) {
+    void add_sint64(pbf_tag_type tag, int64_t value) {
         add_tagged_varint(tag, encode_zigzag64(value));
     }
 
@@ -377,7 +398,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_uint64(pbf_tag_type tag, uint64_t value) {
+    void add_uint64(pbf_tag_type tag, uint64_t value) {
         add_tagged_varint(tag, value);
     }
 
@@ -387,7 +408,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_fixed32(pbf_tag_type tag, uint32_t value) {
+    void add_fixed32(pbf_tag_type tag, uint32_t value) {
         add_field(tag, pbf_wire_type::fixed32);
         add_fixed<uint32_t>(value);
     }
@@ -398,7 +419,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sfixed32(pbf_tag_type tag, int32_t value) {
+    void add_sfixed32(pbf_tag_type tag, int32_t value) {
         add_field(tag, pbf_wire_type::fixed32);
         add_fixed<int32_t>(value);
     }
@@ -409,7 +430,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_fixed64(pbf_tag_type tag, uint64_t value) {
+    void add_fixed64(pbf_tag_type tag, uint64_t value) {
         add_field(tag, pbf_wire_type::fixed64);
         add_fixed<uint64_t>(value);
     }
@@ -420,7 +441,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_sfixed64(pbf_tag_type tag, int64_t value) {
+    void add_sfixed64(pbf_tag_type tag, int64_t value) {
         add_field(tag, pbf_wire_type::fixed64);
         add_fixed<int64_t>(value);
     }
@@ -431,7 +452,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_float(pbf_tag_type tag, float value) {
+    void add_float(pbf_tag_type tag, float value) {
         add_field(tag, pbf_wire_type::fixed32);
         add_fixed<float>(value);
     }
@@ -442,7 +463,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_double(pbf_tag_type tag, double value) {
+    void add_double(pbf_tag_type tag, double value) {
         add_field(tag, pbf_wire_type::fixed64);
         add_fixed<double>(value);
     }
@@ -454,7 +475,7 @@ public:
      * @param value Pointer to value to be written
      * @param size Number of bytes to be written
      */
-    inline void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) {
+    void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) {
         protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
         protozero_assert(m_data);
         protozero_assert(size <= std::numeric_limits<pbf_length_type>::max());
@@ -468,7 +489,17 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_bytes(pbf_tag_type tag, const std::string& value) {
+    void add_bytes(pbf_tag_type tag, const data_view& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "bytes" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    void add_bytes(pbf_tag_type tag, const std::string& value) {
         add_bytes(tag, value.data(), value.size());
     }
 
@@ -479,7 +510,7 @@ public:
      * @param value Pointer to value to be written
      * @param size Number of bytes to be written
      */
-    inline void add_string(pbf_tag_type tag, const char* value, std::size_t size) {
+    void add_string(pbf_tag_type tag, const char* value, std::size_t size) {
         add_bytes(tag, value, size);
     }
 
@@ -489,7 +520,17 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written
      */
-    inline void add_string(pbf_tag_type tag, const std::string& value) {
+    void add_string(pbf_tag_type tag, const data_view& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "string" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    void add_string(pbf_tag_type tag, const std::string& value) {
         add_bytes(tag, value.data(), value.size());
     }
 
@@ -500,7 +541,7 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Pointer to value to be written
      */
-    inline void add_string(pbf_tag_type tag, const char* value) {
+    void add_string(pbf_tag_type tag, const char* value) {
         add_bytes(tag, value, std::strlen(value));
     }
 
@@ -511,7 +552,7 @@ public:
      * @param value Pointer to message to be written
      * @param size Length of the message
      */
-    inline void add_message(pbf_tag_type tag, const char* value, std::size_t size) {
+    void add_message(pbf_tag_type tag, const char* value, std::size_t size) {
         add_bytes(tag, value, size);
     }
 
@@ -521,7 +562,17 @@ public:
      * @param tag Tag (field number) of the field
      * @param value Value to be written. The value must be a complete message.
      */
-    inline void add_message(pbf_tag_type tag, const std::string& value) {
+    void add_message(pbf_tag_type tag, const data_view& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "message" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written. The value must be a complete message.
+     */
+    void add_message(pbf_tag_type tag, const std::string& value) {
         add_bytes(tag, value.data(), value.size());
     }
 
@@ -535,126 +586,126 @@ public:
     /**
      * Add "repeated packed bool" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to bool.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed enum" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed int32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed sint32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_svarint(tag, first, last);
     }
 
     /**
      * Add "repeated packed uint32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed int64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed sint64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_svarint(tag, first, last);
     }
 
     /**
      * Add "repeated packed uint64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_varint(tag, first, last);
     }
 
     /**
      * Add "repeated packed fixed32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<uint32_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -662,14 +713,14 @@ public:
     /**
      * Add "repeated packed sfixed32" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int32_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<int32_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -677,14 +728,14 @@ public:
     /**
      * Add "repeated packed fixed64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to uint64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<uint64_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -692,14 +743,14 @@ public:
     /**
      * Add "repeated packed sfixed64" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to int64_t.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<int64_t, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -707,14 +758,14 @@ public:
     /**
      * Add "repeated packed float" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to float.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<float, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -722,14 +773,14 @@ public:
     /**
      * Add "repeated packed double" field to data.
      *
-     * @tparam InputIterator An type satisfying the InputIterator concept.
+     * @tparam InputIterator A type satisfying the InputIterator concept.
      *         Dereferencing the iterator must yield a type assignable to double.
      * @param tag Tag (field number) of the field
      * @param first Iterator pointing to the beginning of the data
      * @param last Iterator pointing one past the end of data
      */
     template <typename InputIterator>
-    inline void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
+    void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
         add_packed_fixed<double, InputIterator>(tag, first, last,
             typename std::iterator_traits<InputIterator>::iterator_category());
     }
@@ -742,6 +793,16 @@ public:
 
 }; // class pbf_writer
 
+/**
+ * Swap two pbf_writer objects.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline void swap(pbf_writer& lhs, pbf_writer& rhs) noexcept {
+    lhs.swap(rhs);
+}
+
 namespace detail {
 
     class packed_field {
@@ -817,19 +878,46 @@ namespace detail {
 
 } // end namespace detail
 
+/// Class for generating packed repeated bool fields.
 using packed_field_bool     = detail::packed_field_varint<bool>;
+
+/// Class for generating packed repeated enum fields.
 using packed_field_enum     = detail::packed_field_varint<int32_t>;
+
+/// Class for generating packed repeated int32 fields.
 using packed_field_int32    = detail::packed_field_varint<int32_t>;
+
+/// Class for generating packed repeated sint32 fields.
 using packed_field_sint32   = detail::packed_field_svarint<int32_t>;
+
+/// Class for generating packed repeated uint32 fields.
 using packed_field_uint32   = detail::packed_field_varint<uint32_t>;
+
+/// Class for generating packed repeated int64 fields.
 using packed_field_int64    = detail::packed_field_varint<int64_t>;
+
+/// Class for generating packed repeated sint64 fields.
 using packed_field_sint64   = detail::packed_field_svarint<int64_t>;
+
+/// Class for generating packed repeated uint64 fields.
 using packed_field_uint64   = detail::packed_field_varint<uint64_t>;
+
+/// Class for generating packed repeated fixed32 fields.
 using packed_field_fixed32  = detail::packed_field_fixed<uint32_t>;
+
+/// Class for generating packed repeated sfixed32 fields.
 using packed_field_sfixed32 = detail::packed_field_fixed<int32_t>;
+
+/// Class for generating packed repeated fixed64 fields.
 using packed_field_fixed64  = detail::packed_field_fixed<uint64_t>;
+
+/// Class for generating packed repeated sfixed64 fields.
 using packed_field_sfixed64 = detail::packed_field_fixed<int64_t>;
+
+/// Class for generating packed repeated float fields.
 using packed_field_float    = detail::packed_field_fixed<float>;
+
+/// Class for generating packed repeated double fields.
 using packed_field_double   = detail::packed_field_fixed<double>;
 
 } // end namespace protozero
diff --git a/include/protozero/types.hpp b/include/protozero/types.hpp
index 6856b3d..8b04638 100644
--- a/include/protozero/types.hpp
+++ b/include/protozero/types.hpp
@@ -16,33 +16,173 @@ documentation.
  * @brief Contains the declaration of low-level types used in the pbf format.
  */
 
+#include <cstddef>
 #include <cstdint>
+#include <cstring>
+#include <string>
+#include <utility>
+
+#include <protozero/config.hpp>
 
 namespace protozero {
 
+/**
+ * The type used for field tags (field numbers).
+ */
+using pbf_tag_type = uint32_t;
+
+/**
+ * The type used to encode type information.
+ * See the table on
+ *    https://developers.google.com/protocol-buffers/docs/encoding
+ */
+enum class pbf_wire_type : uint32_t {
+    varint           = 0, // int32/64, uint32/64, sint32/64, bool, enum
+    fixed64          = 1, // fixed64, sfixed64, double
+    length_delimited = 2, // string, bytes, embedded messages,
+                            // packed repeated fields
+    fixed32          = 5, // fixed32, sfixed32, float
+    unknown          = 99 // used for default setting in this library
+};
+
+/**
+ * The type used for length values, such as the length of a field.
+ */
+using pbf_length_type = uint32_t;
+
+#ifdef PROTOZERO_USE_VIEW
+using data_view = PROTOZERO_USE_VIEW;
+#else
+
+/**
+ * Holds a pointer to some data and a length.
+ *
+ * This class is supposed to be compatible with the std::string_view
+ * that will be available in C++17.
+ */
+class data_view {
+
+    const char* m_data;
+    std::size_t m_size;
+
+public:
+
+    /**
+     * Default constructor. Construct an empty data_view.
+     */
+    constexpr data_view() noexcept
+        : m_data(nullptr),
+          m_size(0) {
+    }
+
+    /**
+     * Create data_view from pointer and size.
+     *
+     * @param data Pointer to the data.
+     * @param size Length of the data.
+     */
+    constexpr data_view(const char* data, std::size_t size) noexcept
+        : m_data(data),
+          m_size(size) {
+    }
+
     /**
-     * The type used for field tags (field numbers).
+     * Create data_view from string.
+     *
+     * @param str String with the data.
      */
-    typedef uint32_t pbf_tag_type;
+    data_view(const std::string& str) noexcept
+        : m_data(str.data()),
+          m_size(str.size()) {
+    }
 
     /**
-     * The type used to encode type information.
-     * See the table on
-     *    https://developers.google.com/protocol-buffers/docs/encoding
+     * Create data_view from zero-terminated string.
+     *
+     * @param data Pointer to the data.
      */
-    enum class pbf_wire_type : uint32_t {
-        varint           = 0, // int32/64, uint32/64, sint32/64, bool, enum
-        fixed64          = 1, // fixed64, sfixed64, double
-        length_delimited = 2, // string, bytes, embedded messages,
-                              // packed repeated fields
-        fixed32          = 5, // fixed32, sfixed32, float
-        unknown          = 99 // used for default setting in this library
-    };
+    data_view(const char* data) noexcept
+        : m_data(data),
+          m_size(std::strlen(data)) {
+    }
 
     /**
-     * The type used for length values, such as the length of a field.
+     * Swap the contents of this object with the other.
+     *
+     * @param other Other object to swap data with.
      */
-    typedef uint32_t pbf_length_type;
+    void swap(data_view& other) noexcept {
+        using std::swap;
+        swap(m_data, other.m_data);
+        swap(m_size, other.m_size);
+    }
+
+    /// Return pointer to data.
+    constexpr const char* data() const noexcept {
+        return m_data;
+    }
+
+    /// Return length of data in bytes.
+    constexpr std::size_t size() const noexcept {
+        return m_size;
+    }
+
+    /**
+     * Convert data view to string.
+     *
+     * @pre Must not be default constructed data_view.
+     */
+    std::string to_string() const {
+        protozero_assert(m_data);
+        return std::string{m_data, m_size};
+    }
+
+    /**
+     * Convert data view to string.
+     *
+     * @pre Must not be default constructed data_view.
+     */
+    explicit operator std::string() const {
+        protozero_assert(m_data);
+        return std::string{m_data, m_size};
+    }
+
+}; // class data_view
+
+/**
+ * Swap two data_view objects.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline void swap(data_view& lhs, data_view& rhs) noexcept {
+    lhs.swap(rhs);
+}
+
+/**
+ * Two data_view instances are equal if they have the same size and the
+ * same content.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator==(data_view& lhs, data_view& rhs) noexcept {
+    return lhs.size() == rhs.size() && !std::strcmp(lhs.data(), rhs.data());
+}
+
+/**
+ * Two data_view instances are not equal if they have different sizes or the
+ * content differs.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator!=(data_view& lhs, data_view& rhs) noexcept {
+    return !(lhs == rhs);
+}
+
+#endif
+
 
 } // end namespace protozero
 
diff --git a/include/protozero/varint.hpp b/include/protozero/varint.hpp
index 4242df9..f6142d1 100644
--- a/include/protozero/varint.hpp
+++ b/include/protozero/varint.hpp
@@ -23,13 +23,54 @@ documentation.
 namespace protozero {
 
 /**
- * The maximum length of a 64bit varint.
+ * The maximum length of a 64 bit varint.
  */
 constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
 
-// from https://github.com/facebook/folly/blob/master/folly/Varint.h
+namespace detail {
+
+    // from https://github.com/facebook/folly/blob/master/folly/Varint.h
+    inline uint64_t decode_varint_impl(const char** data, const char* end) {
+        const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
+        const int8_t* iend = reinterpret_cast<const int8_t*>(end);
+        const int8_t* p = begin;
+        uint64_t val = 0;
+
+        if (iend - begin >= max_varint_length) {  // fast path
+            do {
+                int64_t b;
+                b = *p++; val  = uint64_t((b & 0x7f)      ); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) <<  7); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break;
+                b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break;
+                throw varint_too_long_exception();
+            } while (false);
+        } else {
+            int shift = 0;
+            while (p != iend && *p < 0) {
+                val |= uint64_t(*p++ & 0x7f) << shift;
+                shift += 7;
+            }
+            if (p == iend) {
+                throw end_of_buffer_exception();
+            }
+            val |= uint64_t(*p++) << shift;
+        }
+
+        *data = reinterpret_cast<const char*>(p);
+        return val;
+    }
+
+} // end namespace detail
+
 /**
- * Decode a 64bit varint.
+ * Decode a 64 bit varint.
  *
  * Strong exception guarantee: if there is an exception the data pointer will
  * not be changed.
@@ -39,54 +80,68 @@ constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
  * @param[in] end Pointer one past the end of the input data.
  * @returns The decoded integer
  * @throws varint_too_long_exception if the varint is longer then the maximum
- *         length that would fit in a 64bit int. Usually this means your data
+ *         length that would fit in a 64 bit int. Usually this means your data
  *         is corrupted or you are trying to read something as a varint that
  *         isn't.
  * @throws end_of_buffer_exception if the *end* of the buffer was reached
  *         before the end of the varint.
  */
 inline uint64_t decode_varint(const char** data, const char* end) {
+    // If this is a one-byte varint, decode it here.
+    if (end != *data && ((**data & 0x80) == 0)) {
+        uint64_t val = uint64_t(**data);
+        ++(*data);
+        return val;
+    }
+    // If this varint is more than one byte, defer to complete implementation.
+    return detail::decode_varint_impl(data, end);
+}
+
+/**
+ * Skip over a varint.
+ *
+ * Strong exception guarantee: if there is an exception the data pointer will
+ * not be changed.
+ *
+ * @param[in,out] data Pointer to pointer to the input data. After the function
+ *        returns this will point to the next data to be read.
+ * @param[in] end Pointer one past the end of the input data.
+ * @throws end_of_buffer_exception if the *end* of the buffer was reached
+ *         before the end of the varint.
+ */
+inline void skip_varint(const char** data, const char* end) {
     const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
     const int8_t* iend = reinterpret_cast<const int8_t*>(end);
     const int8_t* p = begin;
-    uint64_t val = 0;
-
-    if (iend - begin >= max_varint_length) {  // fast path
-        do {
-            int64_t b;
-            b = *p++; val  = uint64_t((b & 0x7f)      ); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) <<  7); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break;
-            b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break;
-            throw varint_too_long_exception();
-        } while (false);
-    } else {
-        int shift = 0;
-        while (p != iend && *p < 0) {
-            val |= uint64_t(*p++ & 0x7f) << shift;
-            shift += 7;
-        }
-        if (p == iend) {
-            throw end_of_buffer_exception();
-        }
-        val |= uint64_t(*p++) << shift;
+
+    while (p != iend && *p < 0) {
+        ++p;
+    }
+
+    if (p >= begin + max_varint_length) {
+        throw varint_too_long_exception();
     }
 
+    if (p == iend) {
+        throw end_of_buffer_exception();
+    }
+
+    ++p;
+
     *data = reinterpret_cast<const char*>(p);
-    return val;
 }
 
 /**
- * Varint-encode a 64bit integer.
+ * Varint encode a 64 bit integer.
+ *
+ * @tparam T An output iterator type.
+ * @param data Output iterator the varint encoded value will be written to
+ *             byte by byte.
+ * @param value The integer that will be encoded.
+ * @throws Any exception thrown by increment or dereference operator on data.
  */
-template <typename OutputIterator>
-inline int write_varint(OutputIterator data, uint64_t value) {
+template <typename T>
+inline int write_varint(T data, uint64_t value) {
     int n=1;
 
     while (value >= 0x80) {
diff --git a/include/protozero/version.hpp b/include/protozero/version.hpp
index 7b60e2e..d427941 100644
--- a/include/protozero/version.hpp
+++ b/include/protozero/version.hpp
@@ -10,13 +10,26 @@ documentation.
 
 *****************************************************************************/
 
+/**
+ * @file version.hpp
+ *
+ * @brief Contains macros defining the protozero version.
+ */
+
+/// The major version number
 #define PROTOZERO_VERSION_MAJOR 1
-#define PROTOZERO_VERSION_MINOR 3
+
+/// The minor version number
+#define PROTOZERO_VERSION_MINOR 4
+
+/// The patch number
 #define PROTOZERO_VERSION_PATCH 0
 
+/// The complete version number
 #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH)
 
-#define PROTOZERO_VERSION_STRING "1.3.0"
+/// Version number as string
+#define PROTOZERO_VERSION_STRING "1.4.0"
 
 
 #endif // PROTOZERO_VERSION_HPP
diff --git a/package.json b/package.json
index 41806a9..679d59c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "protozero",
-    "version": "1.3.0",
+    "version": "1.4.0",
     "description": "Minimalist protocol buffer decoder and encoder in C++",
     "main": "./package.json",
     "repository"   :  {
diff --git a/test/include/packed_access.hpp b/test/include/packed_access.hpp
new file mode 100644
index 0000000..3d8b98d
--- /dev/null
+++ b/test/include/packed_access.hpp
@@ -0,0 +1,234 @@
+
+#define PBF_TYPE_NAME PROTOZERO_TEST_STRING(PBF_TYPE)
+#define GET_TYPE PROTOZERO_TEST_CONCAT(get_packed_, PBF_TYPE)
+#define ADD_TYPE PROTOZERO_TEST_CONCAT(add_packed_, PBF_TYPE)
+
+using packed_field_type = PROTOZERO_TEST_CONCAT(protozero::packed_field_, PBF_TYPE);
+
+TEST_CASE("read repeated packed field: " PBF_TYPE_NAME) {
+
+    // Run these tests twice, the second time we basically move the data
+    // one byte down in the buffer. It doesn't matter how the data or buffer
+    // is aligned before that, in at least one of these cases the ints will
+    // not be aligned properly. So we test that even in that case the ints
+    // will be extracted properly.
+
+    for (std::string::size_type n = 0; n < 2; ++n) {
+
+        std::string abuffer;
+        abuffer.reserve(1000);
+        abuffer.append(n, '\0');
+
+        SECTION("empty") {
+            abuffer.append(load_data("repeated_packed_" PBF_TYPE_NAME "/data-empty"));
+
+            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+
+            REQUIRE(!item.next());
+        }
+
+        SECTION("one") {
+            abuffer.append(load_data("repeated_packed_" PBF_TYPE_NAME "/data-one"));
+
+            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+
+            REQUIRE(item.next());
+            const auto it_range = item.GET_TYPE();
+            REQUIRE(!item.next());
+
+            REQUIRE(it_range.begin() != it_range.end());
+            REQUIRE(*it_range.begin() == 17);
+            REQUIRE(std::next(it_range.begin()) == it_range.end());
+        }
+
+        SECTION("many") {
+            abuffer.append(load_data("repeated_packed_" PBF_TYPE_NAME "/data-many"));
+
+            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+
+            REQUIRE(item.next());
+            const auto it_range = item.GET_TYPE();
+            REQUIRE(!item.next());
+
+            auto it = it_range.begin();
+            REQUIRE(it != it_range.end());
+            REQUIRE(*it++ ==   17);
+            REQUIRE(*it++ ==  200);
+            REQUIRE(*it++ ==    0);
+            REQUIRE(*it++ ==    1);
+            REQUIRE(*it++ == std::numeric_limits<cpp_type>::max());
+#if PBF_TYPE_IS_SIGNED
+            REQUIRE(*it++ == -200);
+            REQUIRE(*it++ ==   -1);
+            REQUIRE(*it++ == std::numeric_limits<cpp_type>::min());
+#endif
+            REQUIRE(it == it_range.end());
+        }
+
+        SECTION("swap iterator range") {
+            abuffer.append(load_data("repeated_packed_" PBF_TYPE_NAME "/data-many"));
+
+            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+
+            REQUIRE(item.next());
+            auto it_range1 = item.GET_TYPE();
+            REQUIRE(!item.next());
+
+            decltype(it_range1) it_range;
+            using std::swap;
+            swap(it_range, it_range1);
+
+            auto it = it_range.begin();
+            REQUIRE(it != it_range.end());
+            REQUIRE(*it++ ==   17);
+            REQUIRE(*it++ ==  200);
+            REQUIRE(*it++ ==    0);
+            REQUIRE(*it++ ==    1);
+            REQUIRE(*it++ == std::numeric_limits<cpp_type>::max());
+        }
+
+        SECTION("end_of_buffer") {
+            abuffer.append(load_data("repeated_packed_" PBF_TYPE_NAME "/data-many"));
+
+            for (std::string::size_type i = 1; i < abuffer.size() - n; ++i) {
+                protozero::pbf_reader item(abuffer.data() + n, i);
+                REQUIRE(item.next());
+                REQUIRE_THROWS_AS(item.GET_TYPE(), protozero::end_of_buffer_exception);
+            }
+        }
+
+    }
+
+}
+
+TEST_CASE("write repeated packed field: " PBF_TYPE_NAME) {
+
+    std::string buffer;
+    protozero::pbf_writer pw(buffer);
+
+    SECTION("empty") {
+        cpp_type data[] = { 17 };
+        pw.ADD_TYPE(1, std::begin(data), std::begin(data) /* !!!! */);
+
+        REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-empty"));
+    }
+
+    SECTION("one") {
+        cpp_type data[] = { 17 };
+        pw.ADD_TYPE(1, std::begin(data), std::end(data));
+
+        REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-one"));
+    }
+
+    SECTION("many") {
+        cpp_type data[] = {
+               17
+            , 200
+            ,   0
+            ,   1
+            ,std::numeric_limits<cpp_type>::max()
+#if PBF_TYPE_IS_SIGNED
+            ,-200
+            ,  -1
+            ,std::numeric_limits<cpp_type>::min()
+#endif
+        };
+        pw.ADD_TYPE(1, std::begin(data), std::end(data));
+
+        REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-many"));
+    }
+
+}
+
+TEST_CASE("write repeated packed field using packed field: " PBF_TYPE_NAME) {
+
+    std::string buffer;
+    protozero::pbf_writer pw(buffer);
+
+    SECTION("empty - should do rollback") {
+        {
+            packed_field_type field{pw, 1};
+        }
+
+        REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-empty"));
+    }
+
+    SECTION("one") {
+        {
+            packed_field_type field{pw, 1};
+            field.add_element(cpp_type(17));
+        }
+
+        REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-one"));
+    }
+
+    SECTION("many") {
+        {
+            packed_field_type field{pw, 1};
+            field.add_element(cpp_type(  17));
+            field.add_element(cpp_type( 200));
+            field.add_element(cpp_type(   0));
+            field.add_element(cpp_type(   1));
+            field.add_element(std::numeric_limits<cpp_type>::max());
+#if PBF_TYPE_IS_SIGNED
+            field.add_element(cpp_type(-200));
+            field.add_element(cpp_type(  -1));
+            field.add_element(std::numeric_limits<cpp_type>::min());
+#endif
+        }
+
+        REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-many"));
+    }
+
+}
+
+TEST_CASE("write from different types of iterators: " PBF_TYPE_NAME) {
+
+    std::string buffer;
+    protozero::pbf_writer pw(buffer);
+
+    SECTION("from uint16_t") {
+#if PBF_TYPE_IS_SIGNED
+        const  int16_t data[] = { 1, 4, 9, 16, 25 };
+#else
+        const uint16_t data[] = { 1, 4, 9, 16, 25 };
+#endif
+
+        pw.ADD_TYPE(1, std::begin(data), std::end(data));
+    }
+
+    SECTION("from string") {
+        std::string data = "1 4 9 16 25";
+        std::stringstream sdata(data);
+
+#if PBF_TYPE_IS_SIGNED
+        using test_type =  int32_t;
+#else
+        using test_type = uint32_t;
+#endif
+
+        std::istream_iterator<test_type> eod;
+        std::istream_iterator<test_type> it(sdata);
+
+        pw.ADD_TYPE(1, it, eod);
+    }
+
+    protozero::pbf_reader item(buffer);
+
+    REQUIRE(item.next());
+    auto it_range = item.GET_TYPE();
+    REQUIRE(!item.next());
+    REQUIRE(std::distance(it_range.begin(), it_range.end()) == 5);
+
+    REQUIRE(it_range.front() ==  1); it_range.drop_front();
+    REQUIRE(it_range.front() ==  4); it_range.drop_front();
+    REQUIRE(it_range.front() ==  9); it_range.drop_front();
+    REQUIRE(it_range.front() == 16); it_range.drop_front();
+    REQUIRE(it_range.front() == 25); it_range.drop_front();
+    REQUIRE(it_range.empty());
+
+    REQUIRE_THROWS_AS(it_range.front(), assert_error);
+    REQUIRE_THROWS_AS(it_range.drop_front(), assert_error);
+
+}
+
diff --git a/test/include/scalar_access.hpp b/test/include/scalar_access.hpp
index f435a87..6137119 100644
--- a/test/include/scalar_access.hpp
+++ b/test/include/scalar_access.hpp
@@ -25,6 +25,16 @@ TEST_CASE("read field: " PBF_TYPE_NAME) {
         REQUIRE(!item.next());
     }
 
+    SECTION("pos200") {
+        const std::string buffer = load_data(PBF_TYPE_NAME "/data-pos200");
+
+        protozero::pbf_reader item(buffer);
+
+        REQUIRE(item.next());
+        REQUIRE(item.GET_TYPE() == 200);
+        REQUIRE(!item.next());
+    }
+
     SECTION("max") {
         const std::string buffer = load_data(PBF_TYPE_NAME "/data-max");
 
@@ -48,6 +58,16 @@ TEST_CASE("read field: " PBF_TYPE_NAME) {
         }
     }
 
+    SECTION("neg200") {
+        const std::string buffer = load_data(PBF_TYPE_NAME "/data-neg200");
+
+        protozero::pbf_reader item(buffer);
+
+        REQUIRE(item.next());
+        REQUIRE(item.GET_TYPE() == -200);
+        REQUIRE(!item.next());
+    }
+
     SECTION("min") {
         if (std::is_signed<cpp_type>::value) {
             const std::string buffer = load_data(PBF_TYPE_NAME "/data-min");
diff --git a/test/include/test.hpp b/test/include/test.hpp
index e88903a..5e3e704 100644
--- a/test/include/test.hpp
+++ b/test/include/test.hpp
@@ -1,4 +1,8 @@
 
+#ifdef _MSC_VER
+# define _SCL_SECURE_NO_WARNINGS
+#endif
+
 #include <catch.hpp>
 
 #include <stdexcept>
diff --git a/test/t/basic/test_cases.cpp b/test/t/basic/test_cases.cpp
index a728810..96b5d55 100644
--- a/test/t/basic/test_cases.cpp
+++ b/test/t/basic/test_cases.cpp
@@ -1,46 +1,42 @@
 
 #include <test.hpp>
 
-TEST_CASE("basic") {
+TEST_CASE("default constructed pbf_reader is okay") {
+    protozero::pbf_reader item;
 
-    SECTION("default constructed pbf message is okay") {
-        protozero::pbf_reader item;
-
-        REQUIRE(item.length() == 0);
-        REQUIRE(!item); // test operator bool()
-        REQUIRE(!item.next());
-    }
+    REQUIRE(item.length() == 0);
+    REQUIRE(!item); // test operator bool()
+    REQUIRE(!item.next());
+}
 
-    SECTION("empty buffer is okay") {
-        const std::string buffer;
-        protozero::pbf_reader item(buffer);
+TEST_CASE("empty buffer in pbf_reader is okay") {
+    const std::string buffer;
+    protozero::pbf_reader item{buffer};
 
-        REQUIRE(item.length() == 0);
-        REQUIRE(!item); // test operator bool()
-        REQUIRE(!item.next());
-    }
+    REQUIRE(item.length() == 0);
+    REQUIRE(!item); // test operator bool()
+    REQUIRE(!item.next());
+}
 
-    SECTION("check every possible value for single byte in buffer") {
-        char buffer[1];
-        for (int i = 0; i <= 255; ++i) {
-            *buffer = static_cast<char>(i);
-            protozero::pbf_reader item(buffer, 1);
-
-            REQUIRE(item.length() == 1);
-            REQUIRE(!!item); // test operator bool()
-            REQUIRE_THROWS({
-                item.next();
-                item.skip();
-            });
-        }
+TEST_CASE("check every possible value for single byte in buffer") {
+    char buffer;
+    for (int i = 0; i <= 255; ++i) {
+        buffer = static_cast<char>(i);
+        protozero::pbf_reader item(&buffer, 1);
+
+        REQUIRE(item.length() == 1);
+        REQUIRE(!!item); // test operator bool()
+        REQUIRE_THROWS({
+            item.next();
+            item.skip();
+        });
     }
+}
 
-    SECTION("illegal wire type") {
-        char buffer[1] = { 1 << 3 | 7 };
-
-        protozero::pbf_reader item(buffer, 1);
-        REQUIRE_THROWS_AS(item.next(), protozero::unknown_pbf_wire_type_exception);
-    }
+TEST_CASE("next() should throw when illegal wire type is encountered") {
+    char buffer = 1 << 3 | 7;
 
+    protozero::pbf_reader item{&buffer, 1};
+    REQUIRE_THROWS_AS(item.next(), protozero::unknown_pbf_wire_type_exception);
 }
 
diff --git a/test/t/complex/test_cases.cpp b/test/t/complex/test_cases.cpp
index d00a605..9772da0 100644
--- a/test/t/complex/test_cases.cpp
+++ b/test/t/complex/test_cases.cpp
@@ -50,7 +50,10 @@ TEST_CASE("read complex data using pbf_reader") {
     SECTION("some") {
         const std::string buffer = load_data("complex/data-some");
 
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item2(buffer);
+        protozero::pbf_reader item;
+        using std::swap;
+        swap(item, item2);
 
         uint32_t sum_of_u = 0;
         while (item.next()) {
@@ -118,10 +121,10 @@ TEST_CASE("read complex data using pbf_reader") {
                     break;
                 }
                 case 7: {
-                    auto pi = item.get_packed_sint32();
+                    const auto pi = item.get_packed_sint32();
                     int32_t sum = 0;
-                    for (auto it = pi.first; it != pi.second; ++it) {
-                        sum += *it;
+                    for (auto val : pi) {
+                        sum += val;
                     }
                     REQUIRE(sum == 5);
                     break;
@@ -196,7 +199,10 @@ TEST_CASE("read complex data using pbf_message") {
     SECTION("some") {
         const std::string buffer = load_data("complex/data-some");
 
-        protozero::pbf_message<TestComplex::Test> item(buffer);
+        protozero::pbf_message<TestComplex::Test> item2(buffer);
+        protozero::pbf_message<TestComplex::Test> item;
+        using std::swap;
+        swap(item, item2);
 
         uint32_t sum_of_u = 0;
         while (item.next()) {
@@ -264,10 +270,10 @@ TEST_CASE("read complex data using pbf_message") {
                     break;
                 }
                 case TestComplex::Test::packed_sint32_d: {
-                    auto pi = item.get_packed_sint32();
+                    const auto pi = item.get_packed_sint32();
                     int32_t sum = 0;
-                    for (auto it = pi.first; it != pi.second; ++it) {
-                        sum += *it;
+                    for (auto val : pi) {
+                        sum += val;
                     }
                     REQUIRE(sum == 5);
                     break;
@@ -349,8 +355,12 @@ TEST_CASE("write complex data using pbf_writer") {
 
     SECTION("some") {
         std::string buffer;
-        protozero::pbf_writer pw(buffer);
-        pw.add_fixed32(1, 12345678);
+        protozero::pbf_writer pw2(buffer);
+        pw2.add_fixed32(1, 12345678);
+
+        protozero::pbf_writer pw;
+        using std::swap;
+        swap(pw, pw2);
 
         std::string submessage;
         protozero::pbf_writer pws(submessage);
@@ -380,9 +390,10 @@ TEST_CASE("write complex data using pbf_writer") {
                     break;
                 }
                 case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
+                    auto view = item.get_view();
+                    protozero::pbf_reader subitem{view};
                     REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
+                    REQUIRE(std::string(subitem.get_view()) == "foobar");
                     REQUIRE(!subitem.next());
                     break;
                 }
@@ -448,10 +459,10 @@ TEST_CASE("write complex data using pbf_writer") {
                     break;
                 }
                 case 7: {
-                    auto pi = item.get_packed_sint32();
+                    const auto pi = item.get_packed_sint32();
                     int32_t sum = 0;
-                    for (auto it = pi.first; it != pi.second; ++it) {
-                        sum += *it;
+                    for (auto val : pi) {
+                        sum += val;
                     }
                     REQUIRE(sum == 5);
                     break;
@@ -508,8 +519,13 @@ TEST_CASE("write complex data using pbf_builder") {
 
     SECTION("some") {
         std::string buffer;
-        protozero::pbf_builder<TestComplex::Test> pw(buffer);
-        pw.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
+        protozero::pbf_builder<TestComplex::Test> pw2(buffer);
+        pw2.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
+
+        std::string dummy_buffer;
+        protozero::pbf_builder<TestComplex::Test> pw(dummy_buffer);
+        using std::swap;
+        swap(pw, pw2);
 
         std::string submessage;
         protozero::pbf_builder<TestComplex::Sub> pws(submessage);
@@ -607,10 +623,10 @@ TEST_CASE("write complex data using pbf_builder") {
                     break;
                 }
                 case 7: {
-                    auto pi = item.get_packed_sint32();
+                    const auto pi = item.get_packed_sint32();
                     int32_t sum = 0;
-                    for (auto it = pi.first; it != pi.second; ++it) {
-                        sum += *it;
+                    for (auto val : pi) {
+                        sum += val;
                     }
                     REQUIRE(sum == 5);
                     break;
diff --git a/test/t/data_view/test_cases.cpp b/test/t/data_view/test_cases.cpp
new file mode 100644
index 0000000..f71b9b1
--- /dev/null
+++ b/test/t/data_view/test_cases.cpp
@@ -0,0 +1,83 @@
+
+#include <test.hpp>
+
+#include <protozero/types.hpp>
+
+TEST_CASE("default constructed data_view") {
+    protozero::data_view view;
+    REQUIRE(view.data() == nullptr);
+    REQUIRE(view.size() == 0);
+}
+
+TEST_CASE("data_view from C string") {
+    protozero::data_view view{"foobar"};
+    REQUIRE(view.data());
+    REQUIRE(view.size() == 6);
+}
+
+TEST_CASE("data_view from std::string") {
+    std::string str{"foobar"};
+    protozero::data_view view{str};
+    REQUIRE(view.data());
+    REQUIRE(view.size() == 6);
+}
+
+TEST_CASE("data_view from ptr, size") {
+    std::string str{"foobar"};
+    protozero::data_view view{str.data(), str.size()};
+    REQUIRE(view.data());
+    REQUIRE(view.size() == 6);
+}
+
+TEST_CASE("convert data_view to std::string") {
+    protozero::data_view view{"foobar"};
+
+    std::string s = std::string(view);
+    REQUIRE(s == "foobar");
+    REQUIRE(std::string(view) == "foobar");
+    REQUIRE(view.to_string() == "foobar");
+}
+
+TEST_CASE("converting default constructed data_view to string fails") {
+    protozero::data_view view;
+    REQUIRE_THROWS_AS({
+        view.to_string();
+    }, assert_error);
+}
+
+TEST_CASE("swapping data_view") {
+    protozero::data_view view1{"foo"};
+    protozero::data_view view2{"bar"};
+
+    REQUIRE(view1.to_string() == "foo");
+    REQUIRE(view2.to_string() == "bar");
+
+    using std::swap;
+    swap(view1, view2);
+
+    REQUIRE(view2.to_string() == "foo");
+    REQUIRE(view1.to_string() == "bar");
+}
+
+TEST_CASE("comparing data_views") {
+    protozero::data_view v1{"foo"};
+    protozero::data_view v2{"bar"};
+    protozero::data_view v3{"foox"};
+    protozero::data_view v4{"foo"};
+
+    REQUIRE_FALSE(v1 == v2);
+    REQUIRE_FALSE(v1 == v3);
+    REQUIRE(v1 == v4);
+    REQUIRE_FALSE(v2 == v3);
+    REQUIRE_FALSE(v2 == v4);
+    REQUIRE_FALSE(v3 == v4);
+
+    REQUIRE(v1 != v2);
+    REQUIRE(v1 != v3);
+    REQUIRE_FALSE(v1 != v4);
+    REQUIRE(v2 != v3);
+    REQUIRE(v2 != v4);
+    REQUIRE(v3 != v4);
+}
+
+
diff --git a/test/t/exceptions/test_cases.cpp b/test/t/exceptions/test_cases.cpp
index 5e302c8..b1063f1 100644
--- a/test/t/exceptions/test_cases.cpp
+++ b/test/t/exceptions/test_cases.cpp
@@ -1,27 +1,23 @@
 
 #include <test.hpp>
 
-TEST_CASE("exceptions messages") {
-
-    SECTION("pbf") {
-        protozero::exception e;
-        REQUIRE(std::string(e.what()) == std::string("pbf exception"));
-    }
-
-    SECTION("varint too long") {
-        protozero::varint_too_long_exception e;
-        REQUIRE(std::string(e.what()) == std::string("varint too long exception"));
-    }
+TEST_CASE("exceptions messages for pbf exception") {
+    protozero::exception e;
+    REQUIRE(std::string{e.what()} == std::string{"pbf exception"});
+}
 
-    SECTION("unknown pbf field type") {
-        protozero::unknown_pbf_wire_type_exception e;
-        REQUIRE(std::string(e.what()) == std::string("unknown pbf field type exception"));
-    }
+TEST_CASE("exceptions messages for varint too long") {
+    protozero::varint_too_long_exception e;
+    REQUIRE(std::string{e.what()} == std::string{"varint too long exception"});
+}
 
-    SECTION("end of buffer") {
-        protozero::end_of_buffer_exception e;
-        REQUIRE(std::string(e.what()) == std::string("end of buffer exception"));
-    }
+TEST_CASE("exceptions messages for unknown pbf field type") {
+    protozero::unknown_pbf_wire_type_exception e;
+    REQUIRE(std::string{e.what()} == std::string{"unknown pbf field type exception"});
+}
 
+TEST_CASE("exceptions messages for end of buffer") {
+    protozero::end_of_buffer_exception e;
+    REQUIRE(std::string{e.what()} == std::string{"end of buffer exception"});
 }
 
diff --git a/test/t/fixed32/data-pos200.pbf b/test/t/fixed32/data-pos200.pbf
new file mode 100644
index 0000000..7dc726e
Binary files /dev/null and b/test/t/fixed32/data-pos200.pbf differ
diff --git a/test/t/fixed32/testcase.cpp b/test/t/fixed32/testcase.cpp
index 8394e75..450b78a 100644
--- a/test/t/fixed32/testcase.cpp
+++ b/test/t/fixed32/testcase.cpp
@@ -11,6 +11,9 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(std::numeric_limits<uint32_t>::max());
     write_to_file(msg, "data-max.pbf");
 }
diff --git a/test/t/fixed64/data-pos200.pbf b/test/t/fixed64/data-pos200.pbf
new file mode 100644
index 0000000..be400ca
Binary files /dev/null and b/test/t/fixed64/data-pos200.pbf differ
diff --git a/test/t/fixed64/testcase.cpp b/test/t/fixed64/testcase.cpp
index eb1eea1..8c6daff 100644
--- a/test/t/fixed64/testcase.cpp
+++ b/test/t/fixed64/testcase.cpp
@@ -11,6 +11,9 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(std::numeric_limits<uint64_t>::max());
     write_to_file(msg, "data-max.pbf");
 }
diff --git a/test/t/int32/data-neg200.pbf b/test/t/int32/data-neg200.pbf
new file mode 100644
index 0000000..29df43f
--- /dev/null
+++ b/test/t/int32/data-neg200.pbf
@@ -0,0 +1 @@
+���������
\ No newline at end of file
diff --git a/test/t/int32/data-pos200.pbf b/test/t/int32/data-pos200.pbf
new file mode 100644
index 0000000..d9eeb99
--- /dev/null
+++ b/test/t/int32/data-pos200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/int32/testcase.cpp b/test/t/int32/testcase.cpp
index 9347b90..f70711f 100644
--- a/test/t/int32/testcase.cpp
+++ b/test/t/int32/testcase.cpp
@@ -11,9 +11,15 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(-1);
     write_to_file(msg, "data-neg.pbf");
 
+    msg.set_i(-200);
+    write_to_file(msg, "data-neg200.pbf");
+
     msg.set_i(std::numeric_limits<int32_t>::max());
     write_to_file(msg, "data-max.pbf");
 
diff --git a/test/t/int64/data-neg200.pbf b/test/t/int64/data-neg200.pbf
new file mode 100644
index 0000000..29df43f
--- /dev/null
+++ b/test/t/int64/data-neg200.pbf
@@ -0,0 +1 @@
+���������
\ No newline at end of file
diff --git a/test/t/int64/data-pos200.pbf b/test/t/int64/data-pos200.pbf
new file mode 100644
index 0000000..d9eeb99
--- /dev/null
+++ b/test/t/int64/data-pos200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/int64/testcase.cpp b/test/t/int64/testcase.cpp
index fd57a23..1c4d65d 100644
--- a/test/t/int64/testcase.cpp
+++ b/test/t/int64/testcase.cpp
@@ -11,9 +11,15 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(-1);
     write_to_file(msg, "data-neg.pbf");
 
+    msg.set_i(-200);
+    write_to_file(msg, "data-neg200.pbf");
+
     msg.set_i(std::numeric_limits<int64_t>::max());
     write_to_file(msg, "data-max.pbf");
 
diff --git a/test/t/message/test_cases.cpp b/test/t/message/test_cases.cpp
index 0ebf28f..c1253f3 100644
--- a/test/t/message/test_cases.cpp
+++ b/test/t/message/test_cases.cpp
@@ -65,6 +65,13 @@ TEST_CASE("write message field") {
         pbf_submessage.add_string(1, "foobar");
     }
 
+    SECTION("string with subwriter with reserved size") {
+        std::string str{"foobar"};
+        auto size = 1 /* tag */ + 1 /* length field */ + str.size();
+        protozero::pbf_writer pbf_submessage(pbf_test, 1, size);
+        pbf_submessage.add_string(1, "foobar");
+    }
+
     REQUIRE(buffer_test == load_data("message/data-message"));
 
 }
diff --git a/test/t/repeated_packed_bool/test_cases.cpp b/test/t/repeated_packed_bool/test_cases.cpp
index 98bf88b..9ce2f79 100644
--- a/test/t/repeated_packed_bool/test_cases.cpp
+++ b/test/t/repeated_packed_bool/test_cases.cpp
@@ -17,12 +17,12 @@ TEST_CASE("read repeated packed bool field") {
         protozero::pbf_reader item(buffer);
 
         REQUIRE(item.next());
-        auto it_pair = item.get_packed_bool();
+        auto it_range = item.get_packed_bool();
         REQUIRE(!item.next());
 
-        REQUIRE(it_pair.first != it_pair.second);
-        REQUIRE(*it_pair.first);
-        REQUIRE(++it_pair.first == it_pair.second);
+        REQUIRE(it_range.begin() != it_range.end());
+        REQUIRE(*it_range.begin());
+        REQUIRE(std::next(it_range.begin()) == it_range.end());
     }
 
     SECTION("many") {
@@ -31,16 +31,16 @@ TEST_CASE("read repeated packed bool field") {
         protozero::pbf_reader item(buffer);
 
         REQUIRE(item.next());
-        auto it_pair = item.get_packed_bool();
+        auto it_range = item.get_packed_bool();
         REQUIRE(!item.next());
 
-        auto it = it_pair.first;
-        REQUIRE(it != it_pair.second);
+        auto it = it_range.begin();
+        REQUIRE(it != it_range.end());
         REQUIRE(*it++);
         REQUIRE(*it++);
         REQUIRE(! *it++);
         REQUIRE(*it++);
-        REQUIRE(it == it_pair.second);
+        REQUIRE(it == it_range.end());
     }
 
     SECTION("end_of_buffer") {
diff --git a/test/t/repeated_packed_double/test_cases.cpp b/test/t/repeated_packed_double/test_cases.cpp
index 059d4b8..f11e797 100644
--- a/test/t/repeated_packed_double/test_cases.cpp
+++ b/test/t/repeated_packed_double/test_cases.cpp
@@ -27,11 +27,11 @@ TEST_CASE("read repeated packed double field") {
             protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
 
             REQUIRE(item.next());
-            auto it_pair = item.get_packed_double();
+            auto it_range = item.get_packed_double();
             REQUIRE(!item.next());
 
-            REQUIRE(*it_pair.first == 17.34);
-            REQUIRE(++it_pair.first == it_pair.second);
+            REQUIRE(*it_range.begin() == 17.34);
+            REQUIRE(std::next(it_range.begin()) == it_range.end());
         }
 
         SECTION("many") {
@@ -39,16 +39,16 @@ TEST_CASE("read repeated packed double field") {
             protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
 
             REQUIRE(item.next());
-            auto it_pair = item.get_packed_double();
+            auto it_range = item.get_packed_double();
             REQUIRE(!item.next());
 
-            auto it = it_pair.first;
+            auto it = it_range.begin();
             REQUIRE(*it++ == 17.34);
             REQUIRE(*it++ ==   0.0);
             REQUIRE(*it++ ==   1.0);
             REQUIRE(*it++ == std::numeric_limits<double>::min());
             REQUIRE(*it++ == std::numeric_limits<double>::max());
-            REQUIRE(it == it_pair.second);
+            REQUIRE(it == it_range.end());
         }
 
         SECTION("end_of_buffer") {
diff --git a/test/t/repeated_packed_enum/test_cases.cpp b/test/t/repeated_packed_enum/test_cases.cpp
index be1473a..1d1196a 100644
--- a/test/t/repeated_packed_enum/test_cases.cpp
+++ b/test/t/repeated_packed_enum/test_cases.cpp
@@ -17,12 +17,12 @@ TEST_CASE("read repeated packed enum field") {
         protozero::pbf_reader item(buffer);
 
         REQUIRE(item.next());
-        auto it_pair = item.get_packed_enum();
+        auto it_range = item.get_packed_enum();
         REQUIRE(!item.next());
 
-        REQUIRE(it_pair.first != it_pair.second);
-        REQUIRE(*it_pair.first == 0 /* BLACK */);
-        REQUIRE(++it_pair.first == it_pair.second);
+        REQUIRE(it_range.begin() != it_range.end());
+        REQUIRE(*it_range.begin() == 0 /* BLACK */);
+        REQUIRE(std::next(it_range.begin()) == it_range.end());
     }
 
     SECTION("many") {
@@ -31,15 +31,15 @@ TEST_CASE("read repeated packed enum field") {
         protozero::pbf_reader item(buffer);
 
         REQUIRE(item.next());
-        auto it_pair = item.get_packed_enum();
+        auto it_range = item.get_packed_enum();
         REQUIRE(!item.next());
 
-        auto it = it_pair.first;
-        REQUIRE(it != it_pair.second);
+        auto it = it_range.begin();
+        REQUIRE(it != it_range.end());
         REQUIRE(*it++ == 0 /* BLACK */);
         REQUIRE(*it++ == 3 /* BLUE */);
         REQUIRE(*it++ == 2 /* GREEN */);
-        REQUIRE(it == it_pair.second);
+        REQUIRE(it == it_range.end());
     }
 
     SECTION("end_of_buffer") {
diff --git a/test/t/repeated_packed_fixed32/data-many.pbf b/test/t/repeated_packed_fixed32/data-many.pbf
index dff236c..eff5226 100644
Binary files a/test/t/repeated_packed_fixed32/data-many.pbf and b/test/t/repeated_packed_fixed32/data-many.pbf differ
diff --git a/test/t/repeated_packed_fixed32/test_cases.cpp b/test/t/repeated_packed_fixed32/test_cases.cpp
index e8fd3a9..fc2a179 100644
--- a/test/t/repeated_packed_fixed32/test_cases.cpp
+++ b/test/t/repeated_packed_fixed32/test_cases.cpp
@@ -1,203 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed fixed32 field") {
+#define PBF_TYPE fixed32
+#define PBF_TYPE_IS_SIGNED 0
+using cpp_type = uint32_t;
 
-    // Run these tests twice, the second time we basically move the data
-    // one byte down in the buffer. It doesn't matter how the data or buffer
-    // is aligned before that, in at least one of these cases the int32s will
-    // not be aligned properly. So we test that even in that case the int32s
-    // will be extracted properly.
-
-    for (std::string::size_type n = 0; n < 2; ++n) {
-
-        std::string abuffer;
-        abuffer.reserve(1000);
-        abuffer.append(n, '\0');
-
-        SECTION("empty") {
-            abuffer.append(load_data("repeated_packed_fixed32/data-empty"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
-
-            REQUIRE(!item.next());
-        }
-
-        SECTION("one") {
-            abuffer.append(load_data("repeated_packed_fixed32/data-one"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
-
-            REQUIRE(item.next());
-            auto it_pair = item.get_packed_fixed32();
-            REQUIRE(!item.next());
-
-            REQUIRE(*it_pair.first == 17UL);
-            REQUIRE(++it_pair.first == it_pair.second);
-        }
-
-        SECTION("many") {
-            abuffer.append(load_data("repeated_packed_fixed32/data-many"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
-
-            REQUIRE(item.next());
-            auto it_pair = item.get_packed_fixed32();
-            REQUIRE(!item.next());
-
-            auto it = it_pair.first;
-            REQUIRE(*it++ == 17UL);
-            REQUIRE(*it++ ==  0UL);
-            REQUIRE(*it++ ==  1UL);
-            REQUIRE(*it++ == std::numeric_limits<uint32_t>::max());
-            REQUIRE(it == it_pair.second);
-        }
-
-        SECTION("end_of_buffer") {
-            abuffer.append(load_data("repeated_packed_fixed32/data-many"));
-
-            for (std::string::size_type i = 1; i < abuffer.size() - n; ++i) {
-                protozero::pbf_reader item(abuffer.data() + n, i);
-                REQUIRE(item.next());
-                REQUIRE_THROWS_AS(item.get_packed_fixed32(), protozero::end_of_buffer_exception);
-            }
-        }
-
-    }
-
-}
-
-TEST_CASE("write repeated packed fixed32 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        uint32_t data[] = { 17UL };
-        pw.add_packed_fixed32(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-empty"));
-    }
-
-    SECTION("one") {
-        uint32_t data[] = { 17UL };
-        pw.add_packed_fixed32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-one"));
-    }
-
-    SECTION("many") {
-        uint32_t data[] = { 17UL, 0UL, 1UL, std::numeric_limits<uint32_t>::max() };
-        pw.add_packed_fixed32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-many"));
-    }
-
-}
-
-TEST_CASE("write repeated packed fixed32 field using packed_field_fixed32") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty - should do rollback") {
-        {
-            protozero::packed_field_fixed32 field{pw, 1};
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-empty"));
-    }
-
-    SECTION("one") {
-        {
-            protozero::packed_field_fixed32 field{pw, 1};
-            field.add_element(17UL);
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-one"));
-    }
-
-    SECTION("one with predefined size") {
-        {
-            protozero::packed_field_fixed32 field{pw, 1, 1};
-            field.add_element(17UL);
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-one"));
-    }
-
-    SECTION("many") {
-        {
-            protozero::packed_field_fixed32 field{pw, 1};
-            field.add_element(17UL);
-            field.add_element(0UL);
-            field.add_element(1UL);
-            field.add_element(std::numeric_limits<uint32_t>::max());
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-many"));
-    }
-
-    SECTION("many with predefined size") {
-        {
-            protozero::packed_field_fixed32 field{pw, 1, 4};
-            field.add_element(17UL);
-            field.add_element(0UL);
-            field.add_element(1UL);
-            field.add_element(std::numeric_limits<uint32_t>::max());
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed32/data-many"));
-    }
-
-    SECTION("failure when not closing properly after zero elements") {
-        protozero::packed_field_fixed32 field{pw, 1};
-        REQUIRE_THROWS_AS({
-            pw.add_fixed32(2, 1234); // dummy values
-        }, assert_error);
-    }
-
-    SECTION("failure when not closing properly after one element") {
-        protozero::packed_field_fixed32 field{pw, 1};
-        field.add_element(17UL);
-        REQUIRE_THROWS_AS({
-            pw.add_fixed32(2, 1234); // dummy values
-        }, assert_error);
-    }
-
-}
-
-TEST_CASE("write from different types of iterators") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("from uint16_t") {
-        uint16_t data[] = { 1, 4, 9, 16, 25 };
-
-        pw.add_packed_fixed32(1, std::begin(data), std::end(data));
-    }
-
-    SECTION("from string") {
-        std::string data = "1 4 9 16 25";
-        std::stringstream sdata(data);
-
-        std::istream_iterator<uint32_t> eod;
-        std::istream_iterator<uint32_t> it(sdata);
-
-        pw.add_packed_fixed32(1, it, eod);
-    }
-
-    protozero::pbf_reader item(buffer);
-
-    REQUIRE(item.next());
-    auto it_pair = item.get_packed_fixed32();
-    REQUIRE(!item.next());
-    REQUIRE(std::distance(it_pair.first, it_pair.second) == 5);
-
-    auto i = it_pair.first;
-    REQUIRE(*i++ ==  1);
-    REQUIRE(*i++ ==  4);
-    REQUIRE(*i++ ==  9);
-    REQUIRE(*i++ == 16);
-    REQUIRE(*i++ == 25);
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_fixed32/testcase.cpp b/test/t/repeated_packed_fixed32/testcase.cpp
index 34237a7..901e6ad 100644
--- a/test/t/repeated_packed_fixed32/testcase.cpp
+++ b/test/t/repeated_packed_fixed32/testcase.cpp
@@ -10,6 +10,7 @@ int main(int c, char *argv[]) {
     msg.add_i(17UL);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200UL);
     msg.add_i(0UL);
     msg.add_i(1UL);
     msg.add_i(std::numeric_limits<uint32_t>::max());
diff --git a/test/t/repeated_packed_fixed64/data-many.pbf b/test/t/repeated_packed_fixed64/data-many.pbf
index dc5cd98..c007cb9 100644
Binary files a/test/t/repeated_packed_fixed64/data-many.pbf and b/test/t/repeated_packed_fixed64/data-many.pbf differ
diff --git a/test/t/repeated_packed_fixed64/test_cases.cpp b/test/t/repeated_packed_fixed64/test_cases.cpp
index 97c14ad..4959fe6 100644
--- a/test/t/repeated_packed_fixed64/test_cases.cpp
+++ b/test/t/repeated_packed_fixed64/test_cases.cpp
@@ -1,94 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed fixed64 field") {
+#define PBF_TYPE fixed64
+#define PBF_TYPE_IS_SIGNED 0
+using cpp_type = uint64_t;
 
-    // Run these tests twice, the second time we basically move the data
-    // one byte down in the buffer. It doesn't matter how the data or buffer
-    // is aligned before that, in at least one of these cases the int64s will
-    // not be aligned properly. So we test that even in that case the int64s
-    // will be extracted properly.
-
-    for (std::string::size_type n = 0; n < 2; ++n) {
-
-        std::string abuffer;
-        abuffer.reserve(1000);
-        abuffer.append(n, '\0');
-
-        SECTION("empty") {
-            abuffer.append(load_data("repeated_packed_fixed64/data-empty"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
-
-            REQUIRE(!item.next());
-        }
-
-        SECTION("one") {
-            abuffer.append(load_data("repeated_packed_fixed64/data-one"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
-
-            REQUIRE(item.next());
-            auto it_pair = item.get_packed_fixed64();
-            REQUIRE(!item.next());
-
-            REQUIRE(*it_pair.first == 17ULL);
-            REQUIRE(++it_pair.first == it_pair.second);
-        }
-
-        SECTION("many") {
-            abuffer.append(load_data("repeated_packed_fixed64/data-many"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
-
-            REQUIRE(item.next());
-            auto it_pair = item.get_packed_fixed64();
-            REQUIRE(!item.next());
-
-            auto it = it_pair.first;
-            REQUIRE(*it++ == 17ULL);
-            REQUIRE(*it++ ==  0ULL);
-            REQUIRE(*it++ ==  1ULL);
-            REQUIRE(*it++ == std::numeric_limits<uint64_t>::max());
-            REQUIRE(it == it_pair.second);
-        }
-
-        SECTION("end_of_buffer") {
-            abuffer.append(load_data("repeated_packed_fixed64/data-many"));
-
-            for (std::string::size_type i = 1; i < abuffer.size() - n; ++i) {
-                protozero::pbf_reader item(abuffer.data() + n, i);
-                REQUIRE(item.next());
-                REQUIRE_THROWS_AS(item.get_packed_fixed64(), protozero::end_of_buffer_exception);
-            }
-        }
-
-    }
-
-}
-
-TEST_CASE("write repeated packed fixed64 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        uint64_t data[] = { 17ULL };
-        pw.add_packed_fixed64(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed64/data-empty"));
-    }
-
-    SECTION("one") {
-        uint64_t data[] = { 17ULL };
-        pw.add_packed_fixed64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed64/data-one"));
-    }
-
-    SECTION("many") {
-        uint64_t data[] = { 17ULL, 0ULL, 1ULL, std::numeric_limits<uint64_t>::max() };
-        pw.add_packed_fixed64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_fixed64/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_fixed64/testcase.cpp b/test/t/repeated_packed_fixed64/testcase.cpp
index 2fc414a..49e9e39 100644
--- a/test/t/repeated_packed_fixed64/testcase.cpp
+++ b/test/t/repeated_packed_fixed64/testcase.cpp
@@ -10,6 +10,7 @@ int main(int c, char *argv[]) {
     msg.add_i(17ULL);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200UL);
     msg.add_i(0ULL);
     msg.add_i(1ULL);
     msg.add_i(std::numeric_limits<uint64_t>::max());
diff --git a/test/t/repeated_packed_float/test_cases.cpp b/test/t/repeated_packed_float/test_cases.cpp
index 9ae03eb..afc09cd 100644
--- a/test/t/repeated_packed_float/test_cases.cpp
+++ b/test/t/repeated_packed_float/test_cases.cpp
@@ -27,11 +27,11 @@ TEST_CASE("read repeated packed float field") {
             protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
 
             REQUIRE(item.next());
-            auto it_pair = item.get_packed_float();
+            auto it_range = item.get_packed_float();
             REQUIRE(!item.next());
 
-            REQUIRE(*it_pair.first == 17.34f);
-            REQUIRE(++it_pair.first == it_pair.second);
+            REQUIRE(*it_range.begin() == 17.34f);
+            REQUIRE(std::next(it_range.begin()) == it_range.end());
         }
 
         SECTION("many") {
@@ -39,16 +39,16 @@ TEST_CASE("read repeated packed float field") {
             protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
 
             REQUIRE(item.next());
-            auto it_pair = item.get_packed_float();
+            auto it_range = item.get_packed_float();
             REQUIRE(!item.next());
 
-            auto it = it_pair.first;
+            auto it = it_range.begin();
             REQUIRE(*it++ == 17.34f);
             REQUIRE(*it++ ==   0.0f);
             REQUIRE(*it++ ==   1.0f);
             REQUIRE(*it++ == std::numeric_limits<float>::min());
             REQUIRE(*it++ == std::numeric_limits<float>::max());
-            REQUIRE(it == it_pair.second);
+            REQUIRE(it == it_range.end());
         }
 
         SECTION("end_of_buffer") {
diff --git a/test/t/repeated_packed_int32/data-many.pbf b/test/t/repeated_packed_int32/data-many.pbf
index 1b28de6..fe5336a 100644
Binary files a/test/t/repeated_packed_int32/data-many.pbf and b/test/t/repeated_packed_int32/data-many.pbf differ
diff --git a/test/t/repeated_packed_int32/test_cases.cpp b/test/t/repeated_packed_int32/test_cases.cpp
index e27cee9..206c61c 100644
--- a/test/t/repeated_packed_int32/test_cases.cpp
+++ b/test/t/repeated_packed_int32/test_cases.cpp
@@ -1,87 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed int32 field") {
+#define PBF_TYPE int32
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int32_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_int32/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_int32/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_int32();
-        REQUIRE(!item.next());
-
-        REQUIRE(it_pair.first != it_pair.second);
-        REQUIRE(*it_pair.first == 17L);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_int32/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_int32();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(it != it_pair.second);
-        REQUIRE(*it++ == 17L);
-        REQUIRE(*it++ ==  0L);
-        REQUIRE(*it++ ==  1L);
-        REQUIRE(*it++ ==  -1L);
-        REQUIRE(*it++ == std::numeric_limits<int32_t>::max());
-        REQUIRE(*it++ == std::numeric_limits<int32_t>::min());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_int32/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_int32(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed int32 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int32_t data[] = { 17L };
-        pw.add_packed_int32(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_int32/data-empty"));
-    }
-
-    SECTION("one") {
-        int32_t data[] = { 17L };
-        pw.add_packed_int32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_int32/data-one"));
-    }
-
-    SECTION("many") {
-        int32_t data[] = { 17L, 0L, 1L, -1L, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
-        pw.add_packed_int32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_int32/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_int32/testcase.cpp b/test/t/repeated_packed_int32/testcase.cpp
index 033aa72..ab9c5f7 100644
--- a/test/t/repeated_packed_int32/testcase.cpp
+++ b/test/t/repeated_packed_int32/testcase.cpp
@@ -10,10 +10,12 @@ int main(int c, char *argv[]) {
     msg.add_i(17L);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200);
     msg.add_i(0L);
     msg.add_i(1L);
-    msg.add_i(-1L);
     msg.add_i(std::numeric_limits<int32_t>::max());
+    msg.add_i(-200);
+    msg.add_i(-1L);
     msg.add_i(std::numeric_limits<int32_t>::min());
     write_to_file(msg, "data-many.pbf");
 }
diff --git a/test/t/repeated_packed_int64/data-many.pbf b/test/t/repeated_packed_int64/data-many.pbf
index 1a7a7f6..0f71921 100644
Binary files a/test/t/repeated_packed_int64/data-many.pbf and b/test/t/repeated_packed_int64/data-many.pbf differ
diff --git a/test/t/repeated_packed_int64/test_cases.cpp b/test/t/repeated_packed_int64/test_cases.cpp
index 2013a22..d5c2beb 100644
--- a/test/t/repeated_packed_int64/test_cases.cpp
+++ b/test/t/repeated_packed_int64/test_cases.cpp
@@ -1,123 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed int64 field") {
+#define PBF_TYPE int64
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int64_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_int64/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_int64/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_int64();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17LL);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_int64/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_int64();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17LL);
-        REQUIRE(*it++ ==  0LL);
-        REQUIRE(*it++ ==  1LL);
-        REQUIRE(*it++ == -1LL);
-        REQUIRE(*it++ == std::numeric_limits<int64_t>::max());
-        REQUIRE(*it++ == std::numeric_limits<int64_t>::min());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_int64/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_int64(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed int64 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int64_t data[] = { 17LL };
-        pw.add_packed_int64(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_int64/data-empty"));
-    }
-
-    SECTION("one") {
-        int64_t data[] = { 17LL };
-        pw.add_packed_int64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_int64/data-one"));
-    }
-
-    SECTION("many") {
-        int64_t data[] = { 17LL, 0LL, 1LL, -1LL, std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::min() };
-        pw.add_packed_int64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_int64/data-many"));
-    }
-
-}
-
-TEST_CASE("write repeated packed int64 field using packed_field_int64") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty - should do rollback") {
-        {
-            protozero::packed_field_int64 field{pw, 1};
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_int64/data-empty"));
-    }
-
-    SECTION("one") {
-        {
-            protozero::packed_field_int64 field{pw, 1};
-            field.add_element(17LL);
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_int64/data-one"));
-    }
-
-    SECTION("many") {
-        {
-            protozero::packed_field_int64 field{pw, 1};
-            field.add_element(17LL);
-            field.add_element( 0LL);
-            field.add_element( 1LL);
-            field.add_element(-1LL);
-            field.add_element(std::numeric_limits<int64_t>::max());
-            field.add_element(std::numeric_limits<int64_t>::min());
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_int64/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_int64/testcase.cpp b/test/t/repeated_packed_int64/testcase.cpp
index e918504..eb4002e 100644
--- a/test/t/repeated_packed_int64/testcase.cpp
+++ b/test/t/repeated_packed_int64/testcase.cpp
@@ -10,10 +10,12 @@ int main(int c, char *argv[]) {
     msg.add_i(17LL);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200);
     msg.add_i(0LL);
     msg.add_i(1LL);
-    msg.add_i(-1LL);
     msg.add_i(std::numeric_limits<int64_t>::max());
+    msg.add_i(-200);
+    msg.add_i(-1LL);
     msg.add_i(std::numeric_limits<int64_t>::min());
     write_to_file(msg, "data-many.pbf");
 }
diff --git a/test/t/repeated_packed_sfixed32/data-many.pbf b/test/t/repeated_packed_sfixed32/data-many.pbf
index b9ee1b4..e6922e6 100644
Binary files a/test/t/repeated_packed_sfixed32/data-many.pbf and b/test/t/repeated_packed_sfixed32/data-many.pbf differ
diff --git a/test/t/repeated_packed_sfixed32/test_cases.cpp b/test/t/repeated_packed_sfixed32/test_cases.cpp
index 34ace54..c8eb8dd 100644
--- a/test/t/repeated_packed_sfixed32/test_cases.cpp
+++ b/test/t/repeated_packed_sfixed32/test_cases.cpp
@@ -1,86 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed sfixed32 field") {
+#define PBF_TYPE sfixed32
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int32_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_sfixed32/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_sfixed32/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sfixed32();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17L);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_sfixed32/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sfixed32();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17L);
-        REQUIRE(*it++ ==  0L);
-        REQUIRE(*it++ ==  1L);
-        REQUIRE(*it++ == -1L);
-        REQUIRE(*it++ == std::numeric_limits<int32_t>::max());
-        REQUIRE(*it++ == std::numeric_limits<int32_t>::min());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_sfixed32/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_sfixed32(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed sfixed32 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int32_t data[] = { 17L };
-        pw.add_packed_sfixed32(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_sfixed32/data-empty"));
-    }
-
-    SECTION("one") {
-        int32_t data[] = { 17L };
-        pw.add_packed_sfixed32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sfixed32/data-one"));
-    }
-
-    SECTION("many") {
-        int32_t data[] = { 17L, 0L, 1L, -1L, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
-
-        pw.add_packed_sfixed32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sfixed32/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_sfixed32/testcase.cpp b/test/t/repeated_packed_sfixed32/testcase.cpp
index 5177b74..1a84cf5 100644
--- a/test/t/repeated_packed_sfixed32/testcase.cpp
+++ b/test/t/repeated_packed_sfixed32/testcase.cpp
@@ -10,10 +10,12 @@ int main(int c, char *argv[]) {
     msg.add_i(17);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200);
     msg.add_i(0);
     msg.add_i(1);
-    msg.add_i(-1);
     msg.add_i(std::numeric_limits<int32_t>::max());
+    msg.add_i(-200);
+    msg.add_i(-1);
     msg.add_i(std::numeric_limits<int32_t>::min());
     write_to_file(msg, "data-many.pbf");
 }
diff --git a/test/t/repeated_packed_sfixed64/data-many.pbf b/test/t/repeated_packed_sfixed64/data-many.pbf
index 9ae5254..5539d4f 100644
Binary files a/test/t/repeated_packed_sfixed64/data-many.pbf and b/test/t/repeated_packed_sfixed64/data-many.pbf differ
diff --git a/test/t/repeated_packed_sfixed64/test_cases.cpp b/test/t/repeated_packed_sfixed64/test_cases.cpp
index ecce063..3597938 100644
--- a/test/t/repeated_packed_sfixed64/test_cases.cpp
+++ b/test/t/repeated_packed_sfixed64/test_cases.cpp
@@ -1,85 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed sfixed64 field") {
+#define PBF_TYPE sfixed64
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int64_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_sfixed64/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_sfixed64/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sfixed64();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17LL);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_sfixed64/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sfixed64();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17LL);
-        REQUIRE(*it++ ==  0LL);
-        REQUIRE(*it++ ==  1LL);
-        REQUIRE(*it++ == -1LL);
-        REQUIRE(*it++ == std::numeric_limits<int64_t>::max());
-        REQUIRE(*it++ == std::numeric_limits<int64_t>::min());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_sfixed64/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_sfixed64(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed sfixed64 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int64_t data[] = { 17L };
-        pw.add_packed_sfixed64(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_sfixed64/data-empty"));
-    }
-
-    SECTION("one") {
-        int64_t data[] = { 17L };
-        pw.add_packed_sfixed64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sfixed64/data-one"));
-    }
-
-    SECTION("many") {
-        int64_t data[] = { 17L, 0L, 1L, -1L, std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::min() };
-        pw.add_packed_sfixed64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sfixed64/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_sfixed64/testcase.cpp b/test/t/repeated_packed_sfixed64/testcase.cpp
index 90cafb9..673a5ea 100644
--- a/test/t/repeated_packed_sfixed64/testcase.cpp
+++ b/test/t/repeated_packed_sfixed64/testcase.cpp
@@ -10,10 +10,12 @@ int main(int c, char *argv[]) {
     msg.add_i(17);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200);
     msg.add_i(0);
     msg.add_i(1);
-    msg.add_i(-1);
     msg.add_i(std::numeric_limits<int64_t>::max());
+    msg.add_i(-200);
+    msg.add_i(-1);
     msg.add_i(std::numeric_limits<int64_t>::min());
     write_to_file(msg, "data-many.pbf");
 }
diff --git a/test/t/repeated_packed_sint32/data-many.pbf b/test/t/repeated_packed_sint32/data-many.pbf
index 3e0bf6f..e65bbd9 100644
Binary files a/test/t/repeated_packed_sint32/data-many.pbf and b/test/t/repeated_packed_sint32/data-many.pbf differ
diff --git a/test/t/repeated_packed_sint32/test_cases.cpp b/test/t/repeated_packed_sint32/test_cases.cpp
index c2e6c7f..4c6a2d8 100644
--- a/test/t/repeated_packed_sint32/test_cases.cpp
+++ b/test/t/repeated_packed_sint32/test_cases.cpp
@@ -1,85 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed sint32 field") {
+#define PBF_TYPE sint32
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int32_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_sint32/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_sint32/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sint32();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17L);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_sint32/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sint32();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17L);
-        REQUIRE(*it++ ==  0L);
-        REQUIRE(*it++ ==  1L);
-        REQUIRE(*it++ == -1L);
-        REQUIRE(*it++ == std::numeric_limits<int32_t>::max());
-        REQUIRE(*it++ == std::numeric_limits<int32_t>::min());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_sint32/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_sint32(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed sint32 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int32_t data[] = { 17L };
-        pw.add_packed_sint32(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_sint32/data-empty"));
-    }
-
-    SECTION("one") {
-        int32_t data[] = { 17L };
-        pw.add_packed_sint32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sint32/data-one"));
-    }
-
-    SECTION("many") {
-        int32_t data[] = { 17L, 0L, 1L, -1L, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
-        pw.add_packed_sint32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sint32/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_sint32/testcase.cpp b/test/t/repeated_packed_sint32/testcase.cpp
index 3423f6d..8145e4f 100644
--- a/test/t/repeated_packed_sint32/testcase.cpp
+++ b/test/t/repeated_packed_sint32/testcase.cpp
@@ -10,10 +10,12 @@ int main(int c, char *argv[]) {
     msg.add_i(17L);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200);
     msg.add_i(0L);
     msg.add_i(1L);
-    msg.add_i(-1L);
     msg.add_i(std::numeric_limits<int32_t>::max());
+    msg.add_i(-200);
+    msg.add_i(-1L);
     msg.add_i(std::numeric_limits<int32_t>::min());
     write_to_file(msg, "data-many.pbf");
 }
diff --git a/test/t/repeated_packed_sint64/data-many.pbf b/test/t/repeated_packed_sint64/data-many.pbf
index b7eb8bf..89df69a 100644
Binary files a/test/t/repeated_packed_sint64/data-many.pbf and b/test/t/repeated_packed_sint64/data-many.pbf differ
diff --git a/test/t/repeated_packed_sint64/test_cases.cpp b/test/t/repeated_packed_sint64/test_cases.cpp
index 0e06ec2..1a3fb27 100644
--- a/test/t/repeated_packed_sint64/test_cases.cpp
+++ b/test/t/repeated_packed_sint64/test_cases.cpp
@@ -1,123 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed sint64 field") {
+#define PBF_TYPE sint64
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int64_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_sint64/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_sint64/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sint64();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17LL);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_sint64/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_sint64();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17LL);
-        REQUIRE(*it++ ==  0LL);
-        REQUIRE(*it++ ==  1LL);
-        REQUIRE(*it++ == -1LL);
-        REQUIRE(*it++ == std::numeric_limits<int64_t>::max());
-        REQUIRE(*it++ == std::numeric_limits<int64_t>::min());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_sint64/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_sint64(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed sint64 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int64_t data[] = { 17L };
-        pw.add_packed_sint64(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_sint64/data-empty"));
-    }
-
-    SECTION("one") {
-        int64_t data[] = { 17L };
-        pw.add_packed_sint64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sint64/data-one"));
-    }
-
-    SECTION("many") {
-        int64_t data[] = { 17L, 0L, 1L, -1L, std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::min() };
-        pw.add_packed_sint64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_sint64/data-many"));
-    }
-
-}
-
-TEST_CASE("write repeated packed sint64 field using packed_field_sint64") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty - should do rollback") {
-        {
-            protozero::packed_field_sint64 field{pw, 1};
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_sint64/data-empty"));
-    }
-
-    SECTION("one") {
-        {
-            protozero::packed_field_sint64 field{pw, 1};
-            field.add_element(17L);
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_sint64/data-one"));
-    }
-
-    SECTION("many") {
-        {
-            protozero::packed_field_sint64 field{pw, 1};
-            field.add_element(17L);
-            field.add_element( 0L);
-            field.add_element( 1L);
-            field.add_element(-1L);
-            field.add_element(std::numeric_limits<int64_t>::max());
-            field.add_element(std::numeric_limits<int64_t>::min());
-        }
-
-        REQUIRE(buffer == load_data("repeated_packed_sint64/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_sint64/testcase.cpp b/test/t/repeated_packed_sint64/testcase.cpp
index 22d12e8..920c3be 100644
--- a/test/t/repeated_packed_sint64/testcase.cpp
+++ b/test/t/repeated_packed_sint64/testcase.cpp
@@ -10,10 +10,12 @@ int main(int c, char *argv[]) {
     msg.add_i(17LL);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200);
     msg.add_i(0LL);
     msg.add_i(1LL);
-    msg.add_i(-1LL);
     msg.add_i(std::numeric_limits<int64_t>::max());
+    msg.add_i(-200);
+    msg.add_i(-1LL);
     msg.add_i(std::numeric_limits<int64_t>::min());
     write_to_file(msg, "data-many.pbf");
 }
diff --git a/test/t/repeated_packed_uint32/data-many.pbf b/test/t/repeated_packed_uint32/data-many.pbf
index 47fb203..c58a7e5 100644
Binary files a/test/t/repeated_packed_uint32/data-many.pbf and b/test/t/repeated_packed_uint32/data-many.pbf differ
diff --git a/test/t/repeated_packed_uint32/test_cases.cpp b/test/t/repeated_packed_uint32/test_cases.cpp
index dc70ebe..d68caba 100644
--- a/test/t/repeated_packed_uint32/test_cases.cpp
+++ b/test/t/repeated_packed_uint32/test_cases.cpp
@@ -1,83 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed uint32 field") {
+#define PBF_TYPE uint32
+#define PBF_TYPE_IS_SIGNED 0
+using cpp_type = uint32_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_uint32/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_uint32/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_uint32();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17UL);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_uint32/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_uint32();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17UL);
-        REQUIRE(*it++ ==  0UL);
-        REQUIRE(*it++ ==  1UL);
-        REQUIRE(*it++ == std::numeric_limits<uint32_t>::max());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_uint32/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_uint32(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed uint32 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        uint32_t data[] = { 17UL };
-        pw.add_packed_uint32(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_uint32/data-empty"));
-    }
-
-    SECTION("one") {
-        uint32_t data[] = { 17UL };
-        pw.add_packed_uint32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_uint32/data-one"));
-    }
-
-    SECTION("many") {
-        uint32_t data[] = { 17UL, 0UL, 1UL, std::numeric_limits<uint32_t>::max() };
-        pw.add_packed_uint32(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_uint32/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_uint32/testcase.cpp b/test/t/repeated_packed_uint32/testcase.cpp
index d228e5f..ec21b63 100644
--- a/test/t/repeated_packed_uint32/testcase.cpp
+++ b/test/t/repeated_packed_uint32/testcase.cpp
@@ -10,6 +10,7 @@ int main(int c, char *argv[]) {
     msg.add_i(17UL);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200UL);
     msg.add_i(0UL);
     msg.add_i(1UL);
     msg.add_i(std::numeric_limits<uint32_t>::max());
diff --git a/test/t/repeated_packed_uint64/data-many.pbf b/test/t/repeated_packed_uint64/data-many.pbf
index bbdfbcf..0e1dcc8 100644
Binary files a/test/t/repeated_packed_uint64/data-many.pbf and b/test/t/repeated_packed_uint64/data-many.pbf differ
diff --git a/test/t/repeated_packed_uint64/test_cases.cpp b/test/t/repeated_packed_uint64/test_cases.cpp
index 6880106..7f2e95a 100644
--- a/test/t/repeated_packed_uint64/test_cases.cpp
+++ b/test/t/repeated_packed_uint64/test_cases.cpp
@@ -1,83 +1,9 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed uint64 field") {
+#define PBF_TYPE uint64
+#define PBF_TYPE_IS_SIGNED 0
+using cpp_type = uint64_t;
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_uint64/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(!item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_uint64/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_uint64();
-        REQUIRE(!item.next());
-
-        REQUIRE(*it_pair.first == 17ULL);
-        REQUIRE(++it_pair.first == it_pair.second);
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_uint64/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_pair = item.get_packed_uint64();
-        REQUIRE(!item.next());
-
-        auto it = it_pair.first;
-        REQUIRE(*it++ == 17ULL);
-        REQUIRE(*it++ ==  0ULL);
-        REQUIRE(*it++ ==  1ULL);
-        REQUIRE(*it++ == std::numeric_limits<uint64_t>::max());
-        REQUIRE(it == it_pair.second);
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_uint64/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_uint64(), protozero::end_of_buffer_exception);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed uint64 field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        uint64_t data[] = { 17UL };
-        pw.add_packed_uint64(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_uint64/data-empty"));
-    }
-
-    SECTION("one") {
-        uint64_t data[] = { 17UL };
-        pw.add_packed_uint64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_uint64/data-one"));
-    }
-
-    SECTION("many") {
-        uint64_t data[] = { 17UL, 0UL, 1UL, std::numeric_limits<uint64_t>::max() };
-        pw.add_packed_uint64(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_uint64/data-many"));
-    }
-
-}
+#include <packed_access.hpp>
 
diff --git a/test/t/repeated_packed_uint64/testcase.cpp b/test/t/repeated_packed_uint64/testcase.cpp
index ffefe09..d77795b 100644
--- a/test/t/repeated_packed_uint64/testcase.cpp
+++ b/test/t/repeated_packed_uint64/testcase.cpp
@@ -10,6 +10,7 @@ int main(int c, char *argv[]) {
     msg.add_i(17ULL);
     write_to_file(msg, "data-one.pbf");
 
+    msg.add_i(200UL);
     msg.add_i(0ULL);
     msg.add_i(1ULL);
     msg.add_i(std::numeric_limits<uint64_t>::max());
diff --git a/test/t/rollback/test_cases.cpp b/test/t/rollback/test_cases.cpp
index 1223006..99ab6d6 100644
--- a/test/t/rollback/test_cases.cpp
+++ b/test/t/rollback/test_cases.cpp
@@ -51,10 +51,10 @@ TEST_CASE("rollback when using packed_field functions") {
 
         msg.next();
         REQUIRE(msg.tag() == 1);
-        auto it_pair = msg.get_packed_sint64();
-        auto it = it_pair.first;
+        auto it_range = msg.get_packed_sint64();
+        auto it = it_range.begin();
         REQUIRE(*it++ == 17L);
-        REQUIRE(it == it_pair.second);
+        REQUIRE(it == it_range.end());
 
         msg.next();
         REQUIRE(msg.tag() == 4);
@@ -86,15 +86,15 @@ TEST_CASE("rollback when using packed_field functions") {
 
         msg.next();
         REQUIRE(msg.tag() == 1);
-        auto it_pair = msg.get_packed_sint64();
-        auto it = it_pair.first;
+        auto it_range = msg.get_packed_sint64();
+        auto it = it_range.begin();
         REQUIRE(*it++ == 17L);
         REQUIRE(*it++ ==  0L);
         REQUIRE(*it++ ==  1L);
         REQUIRE(*it++ == -1L);
         REQUIRE(*it++ == std::numeric_limits<int64_t>::max());
         REQUIRE(*it++ == std::numeric_limits<int64_t>::min());
-        REQUIRE(it == it_pair.second);
+        REQUIRE(it == it_range.end());
 
         msg.next();
         REQUIRE(msg.tag() == 4);
diff --git a/test/t/sfixed32/data-neg200.pbf b/test/t/sfixed32/data-neg200.pbf
new file mode 100644
index 0000000..19d26f1
--- /dev/null
+++ b/test/t/sfixed32/data-neg200.pbf
@@ -0,0 +1 @@
+
8���
\ No newline at end of file
diff --git a/test/t/sfixed32/data-pos200.pbf b/test/t/sfixed32/data-pos200.pbf
new file mode 100644
index 0000000..7dc726e
Binary files /dev/null and b/test/t/sfixed32/data-pos200.pbf differ
diff --git a/test/t/sfixed32/testcase.cpp b/test/t/sfixed32/testcase.cpp
index 03be3e0..e9f21ce 100644
--- a/test/t/sfixed32/testcase.cpp
+++ b/test/t/sfixed32/testcase.cpp
@@ -11,9 +11,15 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(-1);
     write_to_file(msg, "data-neg.pbf");
 
+    msg.set_i(-200);
+    write_to_file(msg, "data-neg200.pbf");
+
     msg.set_i(std::numeric_limits<int32_t>::max());
     write_to_file(msg, "data-max.pbf");
 
diff --git a/test/t/sfixed64/data-neg200.pbf b/test/t/sfixed64/data-neg200.pbf
new file mode 100644
index 0000000..6460225
--- /dev/null
+++ b/test/t/sfixed64/data-neg200.pbf
@@ -0,0 +1 @@
+	8�������
\ No newline at end of file
diff --git a/test/t/sfixed64/data-pos200.pbf b/test/t/sfixed64/data-pos200.pbf
new file mode 100644
index 0000000..be400ca
Binary files /dev/null and b/test/t/sfixed64/data-pos200.pbf differ
diff --git a/test/t/sfixed64/testcase.cpp b/test/t/sfixed64/testcase.cpp
index b2b50d8..aca3093 100644
--- a/test/t/sfixed64/testcase.cpp
+++ b/test/t/sfixed64/testcase.cpp
@@ -11,9 +11,15 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(-1);
     write_to_file(msg, "data-neg.pbf");
 
+    msg.set_i(-200);
+    write_to_file(msg, "data-neg200.pbf");
+
     msg.set_i(std::numeric_limits<int64_t>::max());
     write_to_file(msg, "data-max.pbf");
 
diff --git a/test/t/sint32/data-neg200.pbf b/test/t/sint32/data-neg200.pbf
new file mode 100644
index 0000000..c1add16
--- /dev/null
+++ b/test/t/sint32/data-neg200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/sint32/data-pos200.pbf b/test/t/sint32/data-pos200.pbf
new file mode 100644
index 0000000..2d26c9a
--- /dev/null
+++ b/test/t/sint32/data-pos200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/sint32/testcase.cpp b/test/t/sint32/testcase.cpp
index cbd7fc1..88f4911 100644
--- a/test/t/sint32/testcase.cpp
+++ b/test/t/sint32/testcase.cpp
@@ -11,9 +11,15 @@ int main(int c, char *argv[]) {
     msg.set_i(1L);
     write_to_file(msg, "data-pos.pbf");
 
-    msg.set_i(-1L);
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
+    msg.set_i(-1);
     write_to_file(msg, "data-neg.pbf");
 
+    msg.set_i(-200);
+    write_to_file(msg, "data-neg200.pbf");
+
     msg.set_i(std::numeric_limits<int32_t>::max());
     write_to_file(msg, "data-max.pbf");
 
diff --git a/test/t/sint64/data-neg200.pbf b/test/t/sint64/data-neg200.pbf
new file mode 100644
index 0000000..c1add16
--- /dev/null
+++ b/test/t/sint64/data-neg200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/sint64/data-pos200.pbf b/test/t/sint64/data-pos200.pbf
new file mode 100644
index 0000000..2d26c9a
--- /dev/null
+++ b/test/t/sint64/data-pos200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/sint64/testcase.cpp b/test/t/sint64/testcase.cpp
index 051afd6..fea56c6 100644
--- a/test/t/sint64/testcase.cpp
+++ b/test/t/sint64/testcase.cpp
@@ -11,9 +11,15 @@ int main(int c, char *argv[]) {
     msg.set_i(1LL);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200LL);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(-1LL);
     write_to_file(msg, "data-neg.pbf");
 
+    msg.set_i(-200LL);
+    write_to_file(msg, "data-neg200.pbf");
+
     msg.set_i(std::numeric_limits<int64_t>::max());
     write_to_file(msg, "data-max.pbf");
 
diff --git a/test/t/skip/test_cases.cpp b/test/t/skip/test_cases.cpp
index f426565..50ea374 100644
--- a/test/t/skip/test_cases.cpp
+++ b/test/t/skip/test_cases.cpp
@@ -1,136 +1,130 @@
 
 #include <test.hpp>
 
-TEST_CASE("skip") {
-
-    SECTION("check that skip() skips the right amount of bytes") {
-
-        // These are all the data files which contain exactly one field.
-        //
-        // Create this list with:
-        //   cd test/t
-        //   find . -name data\*pbf -not -empty | sed -e 's/^.\/\(.*\).pbf/"\1",/'
-        // and then remove everything manually that contains more than one
-        // field.
-        const std::vector<std::string> filenames = {
-            "bool/data-also-true",
-            "bool/data-false",
-            "bool/data-still-true",
-            "bool/data-true",
-            "bytes/data-binary",
-            "bytes/data-empty",
-            "bytes/data-one",
-            "bytes/data-string",
-            "double/data-neg",
-            "double/data-pos",
-            "double/data-zero",
-            "enum/data-black",
-            "enum/data-blue",
-            "fixed32/data-max",
-            "fixed32/data-pos",
-            "fixed32/data-zero",
-            "fixed64/data-max",
-            "fixed64/data-pos",
-            "fixed64/data-zero",
-            "float/data-neg",
-            "float/data-pos",
-            "float/data-zero",
-            "int32/data-max",
-            "int32/data-min",
-            "int32/data-neg",
-            "int32/data-pos",
-            "int32/data-zero",
-            "int64/data-max",
-            "int64/data-min",
-            "int64/data-neg",
-            "int64/data-pos",
-            "int64/data-zero",
-            "message/data-message",
-            "repeated/data-one",
-            "repeated_packed_fixed32/data-many",
-            "repeated_packed_fixed32/data-one",
-            "repeated_packed_fixed64/data-many",
-            "repeated_packed_fixed64/data-one",
-            "repeated_packed_int32/data-many",
-            "repeated_packed_int32/data-one",
-            "repeated_packed_int64/data-many",
-            "repeated_packed_int64/data-one",
-            "repeated_packed_sfixed32/data-many",
-            "repeated_packed_sfixed32/data-one",
-            "repeated_packed_sfixed64/data-many",
-            "repeated_packed_sfixed64/data-one",
-            "repeated_packed_sint32/data-many",
-            "repeated_packed_sint32/data-one",
-            "repeated_packed_sint64/data-many",
-            "repeated_packed_sint64/data-one",
-            "repeated_packed_uint32/data-many",
-            "repeated_packed_uint32/data-one",
-            "repeated_packed_uint64/data-many",
-            "repeated_packed_uint64/data-one",
-            "sfixed32/data-max",
-            "sfixed32/data-min",
-            "sfixed32/data-zero",
-            "sfixed64/data-max",
-            "sfixed64/data-min",
-            "sfixed64/data-zero",
-            "sint32/data-max",
-            "sint32/data-min",
-            "sint32/data-neg",
-            "sint32/data-pos",
-            "sint32/data-zero",
-            "sint64/data-max",
-            "sint64/data-min",
-            "sint64/data-neg",
-            "sint64/data-pos",
-            "sint64/data-zero",
-            "string/data-empty",
-            "string/data-one",
-            "string/data-string",
-            "tags/data-tag-1",
-            "tags/data-tag-200000",
-            "tags/data-tag-200",
-            "tags/data-tag-max",
-            "uint32/data-max",
-            "uint32/data-pos",
-            "uint32/data-zero",
-            "uint64/data-max",
-            "uint64/data-pos",
-            "uint64/data-zero",
-        };
-
-        for (const auto& filename : filenames) {
-            const std::string buffer = load_data(filename);
-
-            protozero::pbf_reader item(buffer);
-
-            REQUIRE(item.next());
-            item.skip();
-            REQUIRE(!item);
-        }
+TEST_CASE("skip() skips the right amount of bytes") {
+
+    // These are all the data files which contain exactly one field.
+    //
+    // Create this list with:
+    //   cd test/t
+    //   find . -name data\*pbf -not -empty | sed -e 's/^.\/\(.*\).pbf/"\1",/'
+    // and then remove everything manually that contains more than one
+    // field.
+    const std::vector<std::string> filenames = {
+        "bool/data-also-true",
+        "bool/data-false",
+        "bool/data-still-true",
+        "bool/data-true",
+        "bytes/data-binary",
+        "bytes/data-empty",
+        "bytes/data-one",
+        "bytes/data-string",
+        "double/data-neg",
+        "double/data-pos",
+        "double/data-zero",
+        "enum/data-black",
+        "enum/data-blue",
+        "fixed32/data-max",
+        "fixed32/data-pos",
+        "fixed32/data-zero",
+        "fixed64/data-max",
+        "fixed64/data-pos",
+        "fixed64/data-zero",
+        "float/data-neg",
+        "float/data-pos",
+        "float/data-zero",
+        "int32/data-max",
+        "int32/data-min",
+        "int32/data-neg",
+        "int32/data-pos",
+        "int32/data-zero",
+        "int64/data-max",
+        "int64/data-min",
+        "int64/data-neg",
+        "int64/data-pos",
+        "int64/data-zero",
+        "message/data-message",
+        "repeated/data-one",
+        "repeated_packed_fixed32/data-many",
+        "repeated_packed_fixed32/data-one",
+        "repeated_packed_fixed64/data-many",
+        "repeated_packed_fixed64/data-one",
+        "repeated_packed_int32/data-many",
+        "repeated_packed_int32/data-one",
+        "repeated_packed_int64/data-many",
+        "repeated_packed_int64/data-one",
+        "repeated_packed_sfixed32/data-many",
+        "repeated_packed_sfixed32/data-one",
+        "repeated_packed_sfixed64/data-many",
+        "repeated_packed_sfixed64/data-one",
+        "repeated_packed_sint32/data-many",
+        "repeated_packed_sint32/data-one",
+        "repeated_packed_sint64/data-many",
+        "repeated_packed_sint64/data-one",
+        "repeated_packed_uint32/data-many",
+        "repeated_packed_uint32/data-one",
+        "repeated_packed_uint64/data-many",
+        "repeated_packed_uint64/data-one",
+        "sfixed32/data-max",
+        "sfixed32/data-min",
+        "sfixed32/data-zero",
+        "sfixed64/data-max",
+        "sfixed64/data-min",
+        "sfixed64/data-zero",
+        "sint32/data-max",
+        "sint32/data-min",
+        "sint32/data-neg",
+        "sint32/data-pos",
+        "sint32/data-zero",
+        "sint64/data-max",
+        "sint64/data-min",
+        "sint64/data-neg",
+        "sint64/data-pos",
+        "sint64/data-zero",
+        "string/data-empty",
+        "string/data-one",
+        "string/data-string",
+        "tags/data-tag-1",
+        "tags/data-tag-200000",
+        "tags/data-tag-200",
+        "tags/data-tag-max",
+        "uint32/data-max",
+        "uint32/data-pos",
+        "uint32/data-zero",
+        "uint64/data-max",
+        "uint64/data-pos",
+        "uint64/data-zero",
+    };
+
+    for (const auto& filename : filenames) {
+        const std::string buffer = load_data(filename);
+
+        protozero::pbf_reader item{buffer};
 
+        REQUIRE(item.next());
+        item.skip();
+        REQUIRE(!item);
     }
+}
 
-    SECTION("check that next() throws on unknown field type") {
-        std::string buffer;
 
-        protozero::pbf_writer pw(buffer);
-        pw.add_fixed32(1, 123);
+TEST_CASE("exceptional cases") {
+
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+    pw.add_fixed32(1, 123);
 
+    SECTION("check that next() throws on unknown field type") {
         buffer[0] += 1; // hack to create illegal field type
 
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
 
         REQUIRE_THROWS_AS(item.next(), protozero::unknown_pbf_wire_type_exception);
     }
 
     SECTION("check that skip() throws on short buffer") {
-        std::string buffer;
-
-        protozero::pbf_writer pw(buffer);
-        pw.add_fixed32(1, 123);
-
         buffer.resize(buffer.size() - 1); // "remove" last byte from buffer
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
 
         REQUIRE(item.next());
         REQUIRE_THROWS_AS(item.skip(), protozero::end_of_buffer_exception);
diff --git a/test/t/string/test_cases.cpp b/test/t/string/test_cases.cpp
index c81d293..050c640 100644
--- a/test/t/string/test_cases.cpp
+++ b/test/t/string/test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-TEST_CASE("read string field") {
+TEST_CASE("read string field using get_string") {
 
     SECTION("empty") {
         const std::string buffer = load_data("string/data-empty");
@@ -45,6 +45,53 @@ TEST_CASE("read string field") {
 
 }
 
+TEST_CASE("read string field using get_view") {
+
+    SECTION("empty") {
+        const std::string buffer = load_data("string/data-empty");
+
+        protozero::pbf_reader item(buffer);
+
+        REQUIRE(item.next());
+        auto v = item.get_view();
+        REQUIRE(v.size() == 0);
+        REQUIRE(!item.next());
+    }
+
+    SECTION("one") {
+        const std::string buffer = load_data("string/data-one");
+
+        protozero::pbf_reader item(buffer);
+
+        REQUIRE(item.next());
+        auto v = item.get_view();
+        REQUIRE(*v.data() == 'x');
+        REQUIRE(v.size() == 1);
+        REQUIRE(!item.next());
+    }
+
+    SECTION("string") {
+        const std::string buffer = load_data("string/data-string");
+
+        protozero::pbf_reader item(buffer);
+
+        REQUIRE(item.next());
+        REQUIRE(std::string(item.get_view()) == "foobar");
+        REQUIRE(!item.next());
+    }
+
+    SECTION("end_of_buffer") {
+        const std::string buffer = load_data("string/data-string");
+
+        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+            protozero::pbf_reader item(buffer.data(), i);
+            REQUIRE(item.next());
+            REQUIRE_THROWS_AS(item.get_view(), protozero::end_of_buffer_exception);
+        }
+    }
+
+}
+
 TEST_CASE("write string field") {
 
     std::string buffer_test;
diff --git a/test/t/uint32/data-pos200.pbf b/test/t/uint32/data-pos200.pbf
new file mode 100644
index 0000000..d9eeb99
--- /dev/null
+++ b/test/t/uint32/data-pos200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/uint32/testcase.cpp b/test/t/uint32/testcase.cpp
index 4a6177b..d5533be 100644
--- a/test/t/uint32/testcase.cpp
+++ b/test/t/uint32/testcase.cpp
@@ -11,6 +11,9 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(std::numeric_limits<uint32_t>::max());
     write_to_file(msg, "data-max.pbf");
 }
diff --git a/test/t/uint64/data-pos200.pbf b/test/t/uint64/data-pos200.pbf
new file mode 100644
index 0000000..d9eeb99
--- /dev/null
+++ b/test/t/uint64/data-pos200.pbf
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/t/uint64/testcase.cpp b/test/t/uint64/testcase.cpp
index 22ee8a8..732bf42 100644
--- a/test/t/uint64/testcase.cpp
+++ b/test/t/uint64/testcase.cpp
@@ -11,6 +11,9 @@ int main(int c, char *argv[]) {
     msg.set_i(1);
     write_to_file(msg, "data-pos.pbf");
 
+    msg.set_i(200);
+    write_to_file(msg, "data-pos200.pbf");
+
     msg.set_i(std::numeric_limits<uint64_t>::max());
     write_to_file(msg, "data-max.pbf");
 }
diff --git a/test/t/varint/test_cases.cpp b/test/t/varint/test_cases.cpp
index 3ada39a..be8423e 100644
--- a/test/t/varint/test_cases.cpp
+++ b/test/t/varint/test_cases.cpp
@@ -1,54 +1,142 @@
 
 #include <test.hpp>
 
+TEST_CASE("max varint length") {
+    REQUIRE(protozero::max_varint_length == 10);
+}
+
 TEST_CASE("varint") {
 
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("encode/decode int32") {
         pw.add_int32(1, 17);
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
         REQUIRE(item.next());
-        REQUIRE(17 == item.get_int32());
+
+        SECTION("get") {
+            REQUIRE(17 == item.get_int32());
+        }
+
+        SECTION("skip") {
+            item.skip();
+        }
+
+        REQUIRE_FALSE(item.next());
     }
 
     SECTION("encode/decode uint32") {
         pw.add_uint32(1, 17U);
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
         REQUIRE(item.next());
-        REQUIRE(17U == item.get_uint32());
+
+        SECTION("get") {
+            REQUIRE(17U == item.get_uint32());
+        }
+
+        SECTION("skip") {
+            item.skip();
+        }
+
+        REQUIRE_FALSE(item.next());
     }
 
     SECTION("encode/decode uint64") {
         pw.add_uint64(1, (1ULL << 40));
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
         REQUIRE(item.next());
-        REQUIRE((1ULL << 40) == item.get_uint64());
+
+        SECTION("get") {
+            REQUIRE((1ULL << 40) == item.get_uint64());
+        }
+
+        SECTION("skip") {
+            item.skip();
+        }
+
+        REQUIRE_FALSE(item.next());
     }
 
     SECTION("short buffer while parsing varint") {
         pw.add_uint64(1, (1ULL << 40));
         buffer.resize(buffer.size() - 1); // "remove" last byte from buffer
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
         REQUIRE(item.next());
-        REQUIRE_THROWS_AS(item.get_uint64(), protozero::end_of_buffer_exception);
+
+        SECTION("get") {
+            REQUIRE_THROWS_AS(item.get_uint64(), protozero::end_of_buffer_exception);
+        }
+
+        SECTION("skip") {
+            REQUIRE_THROWS_AS(item.skip(), protozero::end_of_buffer_exception);
+        }
     }
 
     SECTION("data corruption in buffer while parsing varint)") {
         pw.add_uint64(1, (1ULL << 20));
         buffer[buffer.size() - 1] += 0x80; // pretend the varint goes on
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
         REQUIRE(item.next());
-        REQUIRE_THROWS_AS(item.get_uint64(), protozero::end_of_buffer_exception);
+
+        SECTION("get") {
+            REQUIRE_THROWS_AS(item.get_uint64(), protozero::end_of_buffer_exception);
+        }
+
+        SECTION("skip") {
+            REQUIRE_THROWS_AS(item.skip(), protozero::end_of_buffer_exception);
+        }
     }
 
     SECTION("data corruption in buffer while parsing varint (max length varint)") {
         pw.add_uint64(1, std::numeric_limits<uint64_t>::max());
         buffer[buffer.size() - 1] += 0x80; // pretend the varint goes on
-        protozero::pbf_reader item(buffer);
+        protozero::pbf_reader item{buffer};
+        REQUIRE(item.next());
+
+        SECTION("get") {
+            REQUIRE_THROWS_AS(item.get_uint64(), protozero::varint_too_long_exception);
+        }
+
+        SECTION("skip") {
+            REQUIRE_THROWS_AS(item.skip(), protozero::varint_too_long_exception);
+        }
+    }
+
+}
+
+TEST_CASE("lots of varints back and forth") {
+
+    std::string buffer;
+
+    for (uint32_t n = 0; n < 70000; ++n) {
+        protozero::pbf_writer pw{buffer};
+        pw.add_uint32(1, n);
+        protozero::pbf_reader item{buffer};
+        REQUIRE(item.next());
+        REQUIRE(n == item.get_uint32());
+        REQUIRE(!item.next());
+        buffer.clear();
+    }
+
+    for (int32_t n = -70000; n < 70000; ++n) {
+        protozero::pbf_writer pw{buffer};
+        pw.add_int32(1, n);
+        protozero::pbf_reader item{buffer};
+        REQUIRE(item.next());
+        REQUIRE(n == item.get_int32());
+        REQUIRE(!item.next());
+        buffer.clear();
+    }
+
+    for (int32_t n = -70000; n < 70000; ++n) {
+        protozero::pbf_writer pw{buffer};
+        pw.add_sint32(1, n);
+        protozero::pbf_reader item{buffer};
         REQUIRE(item.next());
-        REQUIRE_THROWS_AS(item.get_uint64(), protozero::varint_too_long_exception);
+        REQUIRE(n == item.get_sint32());
+        REQUIRE(!item.next());
+        buffer.clear();
     }
 
 }
diff --git a/test/t/vector_tile/test_cases.cpp b/test/t/vector_tile/test_cases.cpp
index 314aeb7..1b31904 100644
--- a/test/t/vector_tile/test_cases.cpp
+++ b/test/t/vector_tile/test_cases.cpp
@@ -28,15 +28,14 @@ std::string get_name(protozero::pbf_reader layer) { // copy!
 
 TEST_CASE("reading vector tiles") {
 
-    SECTION("iterate over message using next()") {
-        const std::string buffer = load_data("vector_tile/data.vector");
-
-        protozero::pbf_reader item(buffer);
+    const std::string buffer = load_data("vector_tile/data.vector");
+    protozero::pbf_reader item{buffer};
+    std::vector<std::string> layer_names;
 
-        std::vector<std::string> layer_names;
+    SECTION("iterate over message using next()") {
         while (item.next()) {
             if (item.tag() == 3) { // repeated message Layer
-                protozero::pbf_reader layer { item.get_message() };
+                protozero::pbf_reader layer{item.get_message()};
                 while (layer.next()) {
                     switch (layer.tag()) {
                         case 1: // required string name
@@ -55,13 +54,8 @@ TEST_CASE("reading vector tiles") {
     }
 
     SECTION("iterate over message using next(type)") {
-        const std::string buffer = load_data("vector_tile/data.vector");
-
-        protozero::pbf_reader item(buffer);
-
-        std::vector<std::string> layer_names;
         while (item.next(3)) { // repeated message Layer
-            protozero::pbf_reader layermsg { item.get_message() };
+            protozero::pbf_reader layermsg{item.get_message()};
             while (layermsg.next(1)) { // required string name
                 layer_names.push_back(layermsg.get_string());
             }
@@ -71,28 +65,25 @@ TEST_CASE("reading vector tiles") {
     }
 
     SECTION("iterate over features in road layer") {
-        const std::string buffer = load_data("vector_tile/data.vector");
-
-        protozero::pbf_reader item(buffer);
 
         int n=0;
         while (item.next(3)) { // repeated message Layer
-            protozero::pbf_reader layer { item.get_message() };
+            protozero::pbf_reader layer{item.get_message()};
             std::string name = get_name(layer);
             if (name == "road") {
                 while (layer.next(2)) { // repeated Feature
                     ++n;
-                    protozero::pbf_reader feature { layer.get_message() };
+                    protozero::pbf_reader feature{layer.get_message()};
                     while (feature.next()) {
                         switch (feature.tag()) {
                             case 1: { // optional uint64 id
-                                auto id = feature.get_uint64();
+                                const auto id = feature.get_uint64();
                                 REQUIRE(id >=   1ULL);
                                 REQUIRE(id <= 504ULL);
                                 break;
                             }
                             case 3: { // optional GeomType
-                                auto geom_type = feature.get_uint32();
+                                const auto geom_type = feature.get_uint32();
                                 REQUIRE(geom_type >= 1UL);
                                 REQUIRE(geom_type <= 3UL);
                                 break;
diff --git a/test/t/wrong_type_access/test_cases.cpp b/test/t/wrong_type_access/test_cases.cpp
index 4ebefe2..e372a34 100644
--- a/test/t/wrong_type_access/test_cases.cpp
+++ b/test/t/wrong_type_access/test_cases.cpp
@@ -5,9 +5,9 @@
 TEST_CASE("check assert on non-varint access to varint") {
     const std::string buffer = load_data("int32/data-zero");
 
-    protozero::pbf_reader item(buffer);
-
+    protozero::pbf_reader item{buffer};
     REQUIRE(item.next());
+
     REQUIRE(item.get_int32() == 0);
     REQUIRE_THROWS_AS(item.get_fixed64(), assert_error);
     REQUIRE_THROWS_AS(item.get_string(), assert_error);
@@ -18,9 +18,9 @@ TEST_CASE("check assert on non-varint access to varint") {
 TEST_CASE("check assert on non-fixed access to fixed64") {
     const std::string buffer = load_data("fixed64/data-zero");
 
-    protozero::pbf_reader item(buffer);
-
+    protozero::pbf_reader item{buffer};
     REQUIRE(item.next());
+
     REQUIRE_THROWS_AS(item.get_int32(), assert_error);
     REQUIRE(item.get_fixed64() == 0);
     REQUIRE_THROWS_AS(item.get_string(), assert_error);
@@ -31,9 +31,9 @@ TEST_CASE("check assert on non-fixed access to fixed64") {
 TEST_CASE("check assert on non-string access to string") {
     const std::string buffer = load_data("string/data-string");
 
-    protozero::pbf_reader item(buffer);
-
+    protozero::pbf_reader item{buffer};
     REQUIRE(item.next());
+
     REQUIRE_THROWS_AS(item.get_int32(), assert_error);
     REQUIRE_THROWS_AS(item.get_fixed64(), assert_error);
     REQUIRE(item.get_string() == "foobar");
@@ -44,9 +44,9 @@ TEST_CASE("check assert on non-string access to string") {
 TEST_CASE("check assert on non-fixed access to fixed32") {
     const std::string buffer = load_data("fixed32/data-zero");
 
-    protozero::pbf_reader item(buffer);
-
+    protozero::pbf_reader item{buffer};
     REQUIRE(item.next());
+
     REQUIRE_THROWS_AS(item.get_int32(), assert_error);
     REQUIRE_THROWS_AS(item.get_fixed64(), assert_error);
     REQUIRE_THROWS_AS(item.get_string(), assert_error);
diff --git a/test/t/zigzag/test_cases.cpp b/test/t/zigzag/test_cases.cpp
index 1773100..e877493 100644
--- a/test/t/zigzag/test_cases.cpp
+++ b/test/t/zigzag/test_cases.cpp
@@ -9,43 +9,35 @@ inline int64_t zz64(int64_t val) {
     return protozero::decode_zigzag64(protozero::encode_zigzag64(val));
 }
 
-TEST_CASE("zigzag") {
-
-    SECTION("some values - 32bit") {
-
-        REQUIRE(protozero::encode_zigzag32( 0L) == 0UL);
-        REQUIRE(protozero::encode_zigzag32(-1L) == 1UL);
-        REQUIRE(protozero::encode_zigzag32( 1L) == 2UL);
-        REQUIRE(protozero::encode_zigzag32(-2L) == 3UL);
-        REQUIRE(protozero::encode_zigzag32( 2L) == 4UL);
-
-    }
-
-    SECTION("some values - 64bit") {
-
-        REQUIRE(protozero::encode_zigzag64( 0LL) == 0ULL);
-        REQUIRE(protozero::encode_zigzag64(-1LL) == 1ULL);
-        REQUIRE(protozero::encode_zigzag64( 1LL) == 2ULL);
-        REQUIRE(protozero::encode_zigzag64(-2LL) == 3ULL);
-        REQUIRE(protozero::encode_zigzag64( 2LL) == 4ULL);
-
-    }
+TEST_CASE("zigzag encode some 32 bit values") {
+    REQUIRE(protozero::encode_zigzag32( 0L) == 0UL);
+    REQUIRE(protozero::encode_zigzag32(-1L) == 1UL);
+    REQUIRE(protozero::encode_zigzag32( 1L) == 2UL);
+    REQUIRE(protozero::encode_zigzag32(-2L) == 3UL);
+    REQUIRE(protozero::encode_zigzag32( 2L) == 4UL);
+}
 
-    SECTION("zigzag and back - 32bit") {
-        REQUIRE(zz32( 0L) ==  0L);
-        REQUIRE(zz32( 1L) ==  1L);
-        REQUIRE(zz32(-1L) == -1L);
-        REQUIRE(zz32(std::numeric_limits<int32_t>::max()) == std::numeric_limits<int32_t>::max());
-        REQUIRE(zz32(std::numeric_limits<int32_t>::min()) == std::numeric_limits<int32_t>::min());
-    }
+TEST_CASE("zigzag encode some 64 bit values") {
+    REQUIRE(protozero::encode_zigzag64( 0LL) == 0ULL);
+    REQUIRE(protozero::encode_zigzag64(-1LL) == 1ULL);
+    REQUIRE(protozero::encode_zigzag64( 1LL) == 2ULL);
+    REQUIRE(protozero::encode_zigzag64(-2LL) == 3ULL);
+    REQUIRE(protozero::encode_zigzag64( 2LL) == 4ULL);
+}
 
-    SECTION("zigzag and back - 64bit") {
-        REQUIRE(zz64( 0LL) ==  0LL);
-        REQUIRE(zz64( 1LL) ==  1LL);
-        REQUIRE(zz64(-1LL) == -1LL);
-        REQUIRE(zz64(std::numeric_limits<int64_t>::max()) == std::numeric_limits<int64_t>::max());
-        REQUIRE(zz64(std::numeric_limits<int64_t>::min()) == std::numeric_limits<int64_t>::min());
-    }
+TEST_CASE("zigzag and back - 32bit") {
+    REQUIRE(zz32( 0L) ==  0L);
+    REQUIRE(zz32( 1L) ==  1L);
+    REQUIRE(zz32(-1L) == -1L);
+    REQUIRE(zz32(std::numeric_limits<int32_t>::max()) == std::numeric_limits<int32_t>::max());
+    REQUIRE(zz32(std::numeric_limits<int32_t>::min()) == std::numeric_limits<int32_t>::min());
+}
 
+TEST_CASE("zigzag and back - 64bit") {
+    REQUIRE(zz64( 0LL) ==  0LL);
+    REQUIRE(zz64( 1LL) ==  1LL);
+    REQUIRE(zz64(-1LL) == -1LL);
+    REQUIRE(zz64(std::numeric_limits<int64_t>::max()) == std::numeric_limits<int64_t>::max());
+    REQUIRE(zz64(std::numeric_limits<int64_t>::min()) == std::numeric_limits<int64_t>::min());
 }
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/protozero.git



More information about the Pkg-grass-devel mailing list