[libosmium] 01/05: New upstream version 2.13.0

Bas Couwenberg sebastic at debian.org
Tue Aug 15 17:27:41 UTC 2017


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

sebastic pushed a commit to branch master
in repository libosmium.

commit 89fd4ab1f5e243e62a769df212997caef5c7d3a1
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Aug 15 19:02:20 2017 +0200

    New upstream version 2.13.0
---
 .codecov.yml                                       |    7 +
 .gitignore                                         |    2 +
 .travis.yml                                        |   25 +-
 CHANGELOG.md                                       |   75 +-
 CMakeLists.txt                                     |   40 +-
 README.md                                          |    5 +-
 examples/CMakeLists.txt                            |    4 +
 examples/osmium_amenity_list.cpp                   |   44 +-
 examples/osmium_area_test.cpp                      |   60 +-
 examples/osmium_change_tags.cpp                    |    4 +-
 examples/osmium_convert.cpp                        |   16 +-
 examples/osmium_create_pois.cpp                    |    6 +-
 examples/osmium_debug.cpp                          |   16 +-
 examples/osmium_dump_internal.cpp                  |    2 +-
 examples/osmium_index_lookup.cpp                   |   16 +-
 examples/osmium_location_cache_create.cpp          |    2 +-
 examples/osmium_road_length.cpp                    |    6 +-
 include/osmium/area/assembler.hpp                  |  230 +---
 include/osmium/area/assembler_config.hpp           |    8 +-
 .../area/{assembler.hpp => assembler_legacy.hpp}   |   84 +-
 include/osmium/area/detail/basic_assembler.hpp     |  135 ++-
 .../area/detail/basic_assembler_with_tags.hpp      |   93 ++
 include/osmium/area/detail/node_ref_segment.hpp    |   35 +-
 include/osmium/area/detail/segment_list.hpp        |   50 +-
 include/osmium/area/geom_assembler.hpp             |    5 +-
 include/osmium/area/multipolygon_collector.hpp     |   16 +-
 ...ygon_collector.hpp => multipolygon_manager.hpp} |  167 ++-
 ...llector.hpp => multipolygon_manager_legacy.hpp} |  148 +--
 include/osmium/area/problem_reporter.hpp           |   12 +
 include/osmium/area/problem_reporter_exception.hpp |    6 +
 include/osmium/area/problem_reporter_ogr.hpp       |    4 +
 include/osmium/area/problem_reporter_stream.hpp    |    6 +
 include/osmium/area/stats.hpp                      |    1 +
 include/osmium/builder/builder.hpp                 |   16 +-
 include/osmium/builder/osm_object_builder.hpp      |  117 +-
 include/osmium/diff_handler.hpp                    |    6 +-
 include/osmium/diff_iterator.hpp                   |    6 +-
 include/osmium/diff_visitor.hpp                    |    6 +-
 include/osmium/dynamic_handler.hpp                 |    9 +-
 include/osmium/experimental/flex_reader.hpp        |    2 +-
 include/osmium/geom/factory.hpp                    |   15 +-
 include/osmium/geom/geos.hpp                       |   22 +-
 include/osmium/geom/mercator_projection.hpp        |    3 +
 include/osmium/geom/projection.hpp                 |    3 +-
 include/osmium/geom/relations.hpp                  |   12 +-
 include/osmium/geom/tile.hpp                       |   26 +-
 include/osmium/geom/wkb.hpp                        |   22 +-
 include/osmium/handler/check_order.hpp             |   23 +-
 include/osmium/handler/disk_store.hpp              |    4 +-
 include/osmium/handler/dump.hpp                    |    4 +-
 include/osmium/handler/node_locations_for_ways.hpp |   14 +-
 include/osmium/index/detail/create_map_with_fd.hpp |    6 +-
 include/osmium/index/detail/mmap_vector_file.hpp   |    6 +-
 include/osmium/index/detail/tmpfile.hpp            |    2 +-
 include/osmium/index/detail/vector_map.hpp         |   15 +-
 include/osmium/index/id_set.hpp                    |   53 +-
 include/osmium/index/index.hpp                     |    3 +-
 include/osmium/index/map.hpp                       |    9 +-
 include/osmium/index/map/all.hpp                   |    1 +
 include/osmium/index/map/flex_mem.hpp              |  285 +++++
 include/osmium/index/node_locations_map.hpp        |    4 +
 include/osmium/index/relations_map.hpp             |   16 +-
 include/osmium/io/bzip2_compression.hpp            |   28 +-
 include/osmium/io/compression.hpp                  |   49 +-
 include/osmium/io/detail/debug_output_format.hpp   |   17 +-
 include/osmium/io/detail/input_format.hpp          |   10 +-
 include/osmium/io/detail/o5m_input_format.hpp      |    8 +-
 include/osmium/io/detail/opl_input_format.hpp      |    9 +-
 include/osmium/io/detail/opl_output_format.hpp     |   15 +-
 include/osmium/io/detail/opl_parser_functions.hpp  |   23 +-
 include/osmium/io/detail/output_format.hpp         |   19 +-
 include/osmium/io/detail/pbf_decoder.hpp           |   94 +-
 include/osmium/io/detail/pbf_input_format.hpp      |   31 +-
 include/osmium/io/detail/pbf_output_format.hpp     |   54 +-
 include/osmium/io/detail/read_write.hpp            |   10 +-
 include/osmium/io/detail/string_table.hpp          |   16 +-
 include/osmium/io/detail/string_util.hpp           |    8 +-
 include/osmium/io/detail/write_thread.hpp          |    2 +-
 include/osmium/io/detail/xml_input_format.hpp      |    2 +-
 include/osmium/io/detail/xml_output_format.hpp     |   17 +-
 include/osmium/io/detail/zlib.hpp                  |    4 +-
 include/osmium/io/file.hpp                         |   32 +-
 include/osmium/io/gzip_compression.hpp             |   26 +-
 include/osmium/io/header.hpp                       |   12 +-
 include/osmium/io/reader.hpp                       |   44 +-
 include/osmium/io/writer.hpp                       |   14 +-
 include/osmium/memory/buffer.hpp                   |   91 +-
 include/osmium/memory/callback_buffer.hpp          |  189 +++
 include/osmium/memory/item.hpp                     |    2 +-
 include/osmium/object_pointer_collection.hpp       |   10 +-
 include/osmium/opl.hpp                             |    2 +-
 include/osmium/osm/area.hpp                        |    4 +-
 include/osmium/osm/changeset.hpp                   |   24 +-
 include/osmium/osm/location.hpp                    |   36 +-
 include/osmium/osm/object.hpp                      |   13 +-
 include/osmium/osm/object_comparisons.hpp          |   33 +-
 include/osmium/osm/relation.hpp                    |    9 +-
 include/osmium/osm/types.hpp                       |    7 +
 include/osmium/osm/types_from_string.hpp           |   16 +-
 include/osmium/relations/collector.hpp             |    7 +-
 include/osmium/relations/manager_util.hpp          |  197 ++++
 include/osmium/relations/members_database.hpp      |  406 +++++++
 include/osmium/relations/relations_database.hpp    |  335 ++++++
 include/osmium/relations/relations_manager.hpp     |  567 +++++++++
 include/osmium/storage/item_stash.hpp              |  342 ++++++
 include/osmium/thread/function_wrapper.hpp         |    3 +-
 include/osmium/thread/pool.hpp                     |   35 +-
 include/osmium/thread/queue.hpp                    |    9 +-
 include/osmium/util/config.hpp                     |    5 +-
 include/osmium/util/file.hpp                       |   26 +-
 include/osmium/util/iterator.hpp                   |   21 +-
 include/osmium/util/memory_mapping.hpp             |   91 +-
 include/osmium/util/options.hpp                    |   12 +-
 include/osmium/util/progress_bar.hpp               |   21 +-
 include/osmium/util/string.hpp                     |   14 +-
 include/osmium/util/verbose_output.hpp             |    4 +-
 include/osmium/version.hpp                         |    6 +-
 include/protozero/pbf_builder.hpp                  |   18 +-
 include/protozero/pbf_message.hpp                  |    4 +
 include/protozero/pbf_reader.hpp                   |    8 +-
 include/protozero/pbf_writer.hpp                   |   11 +
 include/protozero/types.hpp                        |    8 +-
 include/protozero/version.hpp                      |    4 +-
 test/CMakeLists.txt                                |   16 +
 test/data-tests/testdata-multipolygon.cpp          |    6 +-
 test/data-tests/testdata-xml.cpp                   |    4 +-
 test/examples/t/amenity_list/CMakeLists.txt        |   10 +-
 test/examples/t/amenity_list/area.osm              |   18 +
 test/examples/t/area_test/CMakeLists.txt           |   25 +
 test/examples/t/area_test/data.osm                 |   34 +
 test/examples/t/change_tags/CMakeLists.txt         |    9 +
 test/examples/t/change_tags/data.osm               |   20 +
 test/examples/t/change_tags/result.osm             |   16 +
 test/examples/t/convert/CMakeLists.txt             |   46 +
 test/examples/t/convert/data.osm                   |   34 +
 test/examples/t/count/CMakeLists.txt               |    8 +
 test/examples/t/count/data.osm                     |   12 +
 test/examples/t/create_pois/CMakeLists.txt         |   13 +
 test/examples/t/debug/CMakeLists.txt               |   55 +
 test/examples/t/debug/changesets.osm               |    7 +
 test/examples/t/debug/data.osm                     |   13 +
 test/examples/t/dump_internal/CMakeLists.txt       |   41 +
 test/examples/t/dump_internal/data.osm             |   34 +
 test/examples/t/filter_discussions/CMakeLists.txt  |   10 +
 test/examples/t/filter_discussions/changesets.osm  |   11 +
 test/examples/t/index_lookup/CMakeLists.txt        |   46 +
 test/examples/t/location_cache/CMakeLists.txt      |   36 +
 test/examples/t/location_cache/data.osm            |    7 +
 test/examples/t/location_cache/way.osm             |    9 +
 test/examples/t/pub_names/CMakeLists.txt           |   22 +-
 .../t/pub_names/{pubs.osm => pub-addr.osm}         |    3 +
 .../t/pub_names/{pubs.osm => pub-node.osm}         |    0
 .../t/pub_names/{pubs.osm => pub-noname.osm}       |    1 -
 test/examples/t/pub_names/pub-way.osm              |   21 +
 test/examples/t/read/CMakeLists.txt                |    4 +
 test/examples/t/read/data.osm                      |   29 +
 test/examples/t/read_with_progress/CMakeLists.txt  |    4 +
 test/examples/t/read_with_progress/data.osm        |   29 +
 test/examples/t/tiles/CMakeLists.txt               |   18 +
 test/include/catch.hpp                             | 1222 +++++++++++++-------
 test/t/area/test_node_ref_segment.cpp              |   10 +-
 test/t/geom/test_geojson.cpp                       |   20 +-
 test/t/geom/test_geos.cpp                          |    2 +-
 test/t/geom/test_mercator.cpp                      |   57 +-
 test/t/geom/test_ogr.cpp                           |    2 +-
 test/t/geom/test_projection.cpp                    |    4 +-
 test/t/geom/test_tile.cpp                          |   28 +-
 test/t/geom/test_wkb.cpp                           |   16 +-
 test/t/geom/test_wkt.cpp                           |   36 +-
 test/t/handler/test_check_order_handler.cpp        |  128 ++
 test/t/handler/test_dynamic_handler.cpp            |  116 ++
 test/t/index/test_file_based_index.cpp             |   78 +-
 test/t/index/test_id_to_location.cpp               |   89 +-
 test/t/io/test_bzip2.cpp                           |    2 +-
 test/t/io/test_compression_factory.cpp             |   27 +-
 test/t/io/test_file_formats.cpp                    |   82 +-
 test/t/io/test_opl_parser.cpp                      |   20 +-
 test/t/io/test_output_iterator.cpp                 |   41 +-
 test/t/io/test_reader.cpp                          |   21 +-
 test/t/io/test_reader_fileformat.cpp               |    2 +-
 test/t/io/test_reader_with_mock_parser.cpp         |    4 +-
 test/t/io/test_string_table.cpp                    |  178 +--
 test/t/io/test_writer.cpp                          |   45 +-
 test/t/io/test_writer_with_mock_compression.cpp    |    6 +-
 test/t/io/test_writer_with_mock_encoder.cpp        |   14 +-
 test/t/memory/test_buffer_basics.cpp               |   85 +-
 test/t/memory/test_buffer_node.cpp                 |    2 +-
 test/t/memory/test_callback_buffer.cpp             |  101 ++
 test/t/memory/test_item.cpp                        |   25 +
 test/t/osm/test_area.cpp                           |    2 +-
 test/t/osm/test_box.cpp                            |  228 ++--
 test/t/osm/test_changeset.cpp                      |    2 +-
 test/t/osm/test_crc.cpp                            |   92 +-
 test/t/osm/test_location.cpp                       |  283 +++--
 test/t/osm/test_node.cpp                           |   25 +-
 test/t/osm/test_node_ref.cpp                       |   34 +-
 test/t/osm/test_object_comparisons.cpp             |   72 +-
 test/t/osm/test_timestamp.cpp                      |  164 ++-
 test/t/osm/test_types_from_string.cpp              |   78 +-
 test/t/osm/test_way.cpp                            |    4 +-
 test/t/relations/data.osm                          |   32 +
 test/t/relations/dupl_member.osm                   |   17 +
 test/t/relations/test_members_database.cpp         |  194 ++++
 test/t/relations/test_read_relations.cpp           |   77 ++
 test/t/relations/test_relations_database.cpp       |  105 ++
 test/t/relations/test_relations_manager.cpp        |  248 ++++
 test/t/storage/test_item_stash.cpp                 |  167 +++
 test/t/tags/test_filter.cpp                        |   22 +-
 test/t/tags/test_operators.cpp                     |   93 +-
 test/t/thread/test_pool.cpp                        |   56 +-
 test/t/thread/test_util.cpp                        |   37 +
 test/t/util/test_cast_with_assert.cpp              |   10 +-
 test/t/util/test_config.cpp                        |   75 ++
 test/t/util/test_delta.cpp                         |   31 +
 test/t/util/test_file.cpp                          |    8 +-
 test/t/util/test_options.cpp                       |  117 +-
 test/t/util/test_string.cpp                        |   57 +-
 test/t/util/test_string_matcher.cpp                |   41 +-
 test/t/util/test_timer_disabled.cpp                |   15 +
 test/t/util/test_timer_enabled.cpp                 |   16 +
 220 files changed, 8044 insertions(+), 2666 deletions(-)

diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000..79204cd
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,7 @@
+
+ignore:
+    - "include/gdalcpp.hpp"
+    - "include/protozero"
+    - "include/utf8"
+    - "test/include/catch.hpp"
+
diff --git a/.gitignore b/.gitignore
index 79e5e65..bfc4ceb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
 *.swp
 .ycm_extra_conf.pyc
+/_build*
 /build
 /libosmium-deps
+/.vs*
diff --git a/.travis.yml b/.travis.yml
index 91ee842..3323963 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -141,6 +141,14 @@ matrix:
           packages: ['g++-6', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
       env: COMPILER='g++-6' BUILD_TYPE='Dev'
 
+    - os: linux
+      compiler: linux-gcc6-coverage
+      addons:
+        apt:
+          sources: ['ubuntu-toolchain-r-test', 'boost-latest']
+          packages: ['g++-6', 'libboost1.55-all-dev', 'libgdal-dev', 'libgeos++-dev', 'libproj-dev', 'libsparsehash-dev', 'spatialite-bin']
+      env: COMPILER='g++-6' BUILD_TYPE='Coverage'
+
 
     # 3/ OSX Clang Builds
     - os: osx
@@ -166,12 +174,12 @@ matrix:
 
 
     - os: osx
-      osx_image: xcode8
+      osx_image: xcode8.3
       compiler: xcode8-clang-release
       env: COMPILER='clang++' BUILD_TYPE='Release'
 
     - os: osx
-      osx_image: xcode8
+      osx_image: xcode8.3
       compiler: xcode8-clang-dev
       env: COMPILER='clang++' BUILD_TYPE='Dev'
 
@@ -195,3 +203,16 @@ before_script:
 script:
   - make VERBOSE=1 && ctest --output-on-failure
 
+after_success:
+  - |
+    if [ "${BUILD_TYPE}" = "Coverage" ]; then
+      curl -S -f https://codecov.io/bash -o codecov
+      chmod +x codecov
+      gcov-${COMPILER#g++-} -p $(find test/CMakeFiles -name '*.o')
+      ./codecov -Z -c -F unit_tests
+      gcov-${COMPILER#g++-} -p $(find test/data-tests -name '*.o')
+      ./codecov -Z -c -F data_tests
+      gcov-${COMPILER#g++-} -p $(find examples -name '*.o')
+      ./codecov -Z -c -F examples
+    fi
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7ddcc3..9d90ae3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,77 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
-## [2.12.2] - 2017-05-3
+## [2.13.0] - 2017-08-15
+
+### Added
+
+- New `RelationsManager` class superseeds the `relations::Collector` class.
+  The new class is much more modular and easier to extend. If you are using
+  the Collector class, you are encouraged to switch.
+- New `MultipolygonManager` based on the `RelationsManager` class superseeds
+  the `MultipolygonCollector` class. The examples have been changed to use the
+  new class and all users are encouraged to switch. There is also a
+  `MultipolygonManagerLegacy` class if you still need old-style multipolygon
+  support (see below).
+- New `FlexMem` index class that works with input files of any size and
+  stores the index in memory. This should now be used as the default index
+  for node location stores. Several example programs now use this index.
+- New `CallbackBuffer` class, basically a convenient wrapper around the
+  `Buffer` class with an additional callback function that is called whenever
+  the buffer is full.
+- Introduce new `ItemStash` class for storing OSM objects in memory.
+- New `osmium::geom::overlaps()` function to check if two `Box` objects
+  overlap.
+- Add function `IdSet::used_memory()` to get estimate of memory used in the
+  set.
+- New `is_defined()` and `is_undefined()` methods on `Location` class.
+- Tests for all provided example programs. (Some tests currently fail
+  on Windows for the `osmium_index_lookup` program.)
+
+### Changed
+
+- The area `Assembler` now doesn't work with old-style multipolygons (those
+  are multipolygon relations with the tags on the outer ways(s) instead of
+  on the relation) any more. Because old-style multipolygons are now (mostly)
+  gone from the OSM database this is usually what you want. The new
+  `AssemblerLegacy` class can be used if you actually need support for
+  old-style multipolygons, for instance if you are working with historical
+  data. (In that case you also need to use the `MultipolygonManagerLegacy`
+  class instead of the `MultipolygonManager` class.)
+- Changes for consistent ordering of OSM data: OSM data can come in any order,
+  but usual OSM files are ordered by type, ID, and version. These changes
+  extend this ordering to negative IDs which are sometimes used for objects
+  that have not been uploaded to the OSM server yet. The negative IDs are
+  ordered now before the positive ones, both in order of their absolute value.
+  This is the same ordering as JOSM uses.
+- Multipolygon assembler now checks for three or more overlapping segments
+  which are always an error and can report them.
+- Enable use of user-provided `thread::Pool` instances in `Reader` and
+  `Writer` for special use cases.
+- Growing a `Buffer` will now work with any capacity parameter, it is
+  always rounded up for proper alignment. Buffer constructor with three
+  arguments will now check that commmitted is not larger than capacity.
+- Updated embedded protozero to 1.5.2.
+- Update version of Catch unit test framework to 1.9.7.
+- And, as always, lots of small code cleanups and more tests.
+
+### Fixed
+
+- Buffers larger than 2^32 bytes do now work.
+- Output coordinate with value of -2^31 correctly.
+- Changeset comments with more than 2^16 characters are now allowed. The new
+  maximum size is 2^32.
+- `ChangesetDiscussionBuilder::add_comment_text()` could fail silently instead
+  of throwing an exception.
+- Changeset bounding boxes are now always output to OSM files (any format)
+  if at least one of the corners is defined. This is needed to handle broken
+  data from the main OSM database which contains such cases. The OPL reader
+  has also been fixed to handle this case.
+- In the example `osmium_location_cache_create`, the index file written is
+  always truncated first.
+
+
+## [2.12.2] - 2017-05-03
 
 ### Added
 
@@ -640,7 +710,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
   Doxygen (up to version 1.8.8). This version contains a workaround to fix
   this.
 
-[unreleased]: https://github.com/osmcode/libosmium/compare/v2.12.2...HEAD
+[unreleased]: https://github.com/osmcode/libosmium/compare/v2.13.0...HEAD
+[2.13.0]: https://github.com/osmcode/libosmium/compare/v2.12.2...v2.13.0
 [2.12.2]: https://github.com/osmcode/libosmium/compare/v2.12.1...v2.12.2
 [2.12.1]: https://github.com/osmcode/libosmium/compare/v2.12.0...v2.12.1
 [2.12.0]: https://github.com/osmcode/libosmium/compare/v2.11.0...v2.12.0
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 264c3ed..3c5e62f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,8 +24,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev;Cover
 project(libosmium)
 
 set(LIBOSMIUM_VERSION_MAJOR 2)
-set(LIBOSMIUM_VERSION_MINOR 12)
-set(LIBOSMIUM_VERSION_PATCH 2)
+set(LIBOSMIUM_VERSION_MINOR 13)
+set(LIBOSMIUM_VERSION_PATCH 0)
 
 set(LIBOSMIUM_VERSION
     "${LIBOSMIUM_VERSION_MAJOR}.${LIBOSMIUM_VERSION_MINOR}.${LIBOSMIUM_VERSION_PATCH}")
@@ -43,8 +43,14 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 
 if(CMAKE_BUILD_TYPE STREQUAL "Dev")
     set(dev_build ON)
+    set(data_test_build ON)
 else()
     set(dev_build OFF)
+    set(data_test_build OFF)
+endif()
+
+if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
+    set(data_test_build ON)
 endif()
 
 option(BUILD_EXAMPLES   "compile example programs" ON)
@@ -52,7 +58,8 @@ option(BUILD_TESTING    "compile unit tests, please run them with ctest" ON)
 
 option(BUILD_HEADERS    "compile every header file on its own" ${dev_build})
 option(BUILD_BENCHMARKS "compile benchmark programs" ${dev_build})
-option(BUILD_DATA_TESTS "compile data tests, please run them with ctest" ${dev_build})
+
+option(BUILD_DATA_TESTS "compile data tests, please run them with ctest" ${data_test_build})
 
 option(INSTALL_GDALCPP   "also install gdalcpp headers" OFF)
 option(INSTALL_PROTOZERO "also install protozero headers" OFF)
@@ -88,11 +95,12 @@ endif()
 #
 #-----------------------------------------------------------------------------
 
-include(CheckCXXCompilerFlag)
-check_cxx_compiler_flag("-fkeep-inline-functions" HAS_KEEP_INLINE_FUNCTIONS)
-if(HAS_KEEP_INLINE_FUNCTIONS)
-    set(extra_coverage_flags_ "-fkeep-inline-functions")
-endif()
+## This leads to all sorts of compile problems, so disable for now
+#include(CheckCXXCompilerFlag)
+#check_cxx_compiler_flag("-fkeep-inline-functions" HAS_KEEP_INLINE_FUNCTIONS)
+#if(HAS_KEEP_INLINE_FUNCTIONS)
+#    set(extra_coverage_flags_ "-fkeep-inline-functions")
+#endif()
 
 set(CMAKE_CXX_FLAGS_COVERAGE
     "-g -O0 -fno-inline-functions -fno-inline --coverage ${extra_coverage_flags_}"
@@ -103,8 +111,8 @@ set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
     CACHE STRING "Flags used by the linker during coverage builds.")
 
 if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
-    if(BUILD_EXAMPLES OR BUILD_HEADERS OR BUILD_BENCHMARKS OR BUILD_DATA_TESTS)
-        message(WARNING "Coverage builds don't work for anything but the unit tests")
+    if(BUILD_EXAMPLES OR BUILD_HEADERS OR BUILD_BENCHMARKS)
+        message(WARNING "Coverage builds don't work for anything but the tests")
     endif()
 
     if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -314,7 +322,13 @@ find_program(CPPCHECK cppcheck)
 if(CPPCHECK)
     message(STATUS "Looking for cppcheck - found")
     set(CPPCHECK_OPTIONS
-        --enable=warning,style,performance,portability,information,missingInclude --force -Uassert)
+        --language=c++
+        --quiet
+        -j4
+        --inline-suppr
+        --enable=warning,style,performance,portability,information,missingInclude
+        --force
+        -Uassert -DPROTOZERO_STRICT_API -DPROTOZERO_USE_BUILTIN_BSWAP -UPROTOZERO_USE_VIEW)
 
     # cpp doesn't find system includes for some reason, suppress that report
     set(CPPCHECK_OPTIONS ${CPPCHECK_OPTIONS} --suppress=missingIncludeSystem)
@@ -473,10 +487,12 @@ if(CLANG_TIDY)
     list(APPEND CT_CHECKS "misc-*"
                          "-misc-argument-comment")
 
-    list(APPEND CT_CHECKS "modernize-*")
+    list(APPEND CT_CHECKS "modernize-*"
+                          "-modernize-make-unique") # not available in C++11
 
     list(APPEND CT_CHECKS "readability-*"
                          "-readability-identifier-naming"
+                         "-readability-implicit-bool-cast"
                          "-readability-named-parameter")
 
     string(REPLACE ";" "," ALL_CHECKS "${CT_CHECKS}")
diff --git a/README.md b/README.md
index ce27fea..b4fc4dc 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,9 @@ A fast and flexible C++ library for working with OpenStreetMap data.
 
 Libosmium works on Linux, Mac OSX and Windows.
 
-[![Build Status](https://secure.travis-ci.org/osmcode/libosmium.svg)](https://travis-ci.org/osmcode/libosmium)
-[![Build status](https://ci.appveyor.com/api/projects/status/github/osmcode/libosmium?svg=true)](https://ci.appveyor.com/project/Mapbox/libosmium)
+[![Travis Build Status](https://secure.travis-ci.org/osmcode/libosmium.svg)](https://travis-ci.org/osmcode/libosmium)
+[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/osmcode/libosmium?svg=true)](https://ci.appveyor.com/project/Mapbox/libosmium)
+[![Coverage Status](https://codecov.io/gh/osmcode/libosmium/branch/master/graph/badge.svg)](https://codecov.io/gh/osmcode/libosmium)
 
 Please see the [Libosmium manual](http://osmcode.org/libosmium/manual.html)
 for more details than this README can provide.
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 43c7277..c4f875f 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -60,6 +60,10 @@ foreach(example ${EXAMPLES})
     add_executable(osmium_${example} "osmium_${example}.cpp")
     set_pthread_on_target(osmium_${example})
     target_link_libraries(osmium_${example} ${OSMIUM_IO_LIBRARIES} ${EXAMPLE_LIBS_${example}})
+    add_test(NAME examples_usage_${example} COMMAND osmium_${example})
+    set_tests_properties(examples_usage_${example} PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^Usage: "
+    )
 endforeach()
 
 
diff --git a/examples/osmium_amenity_list.cpp b/examples/osmium_amenity_list.cpp
index 502b44b..7e8d79a 100644
--- a/examples/osmium_amenity_list.cpp
+++ b/examples/osmium_amenity_list.cpp
@@ -9,7 +9,7 @@
   DEMONSTRATES USE OF:
   * file input
   * location indexes and the NodeLocationsForWays handler
-  * the MultipolygonCollector and Assembler to assemble areas (multipolygons)
+  * the MultipolygonManager and Assembler to assemble areas (multipolygons)
   * your own handler that works with areas (multipolygons)
   * accessing tags
   * osmium::geom::Coordinates
@@ -29,17 +29,22 @@
 #include <iostream> // for std::cerr
 #include <string>   // for std::string
 
-// For memory based sparse index
-#include <osmium/index/map/sparse_mem_array.hpp>
+// For the location index. There are different types of indexes available.
+// This will work for all input files keeping the index in memory.
+#include <osmium/index/map/flex_mem.hpp>
 
 // For the NodeLocationForWays handler
 #include <osmium/handler/node_locations_for_ways.hpp>
-using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+
+// The type of index used. This must match the include file above
+using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
+
+// The location handler always depends on the index type
 using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
 
 // For assembling multipolygons
 #include <osmium/area/assembler.hpp>
-#include <osmium/area/multipolygon_collector.hpp>
+#include <osmium/area/multipolygon_manager.hpp>
 
 // Allow any format of input files (XML, PBF, ...)
 #include <osmium/io/any_input.hpp>
@@ -108,8 +113,8 @@ int main(int argc, char* argv[]) {
         std::exit(1);
     }
 
-    // The input file name
-    const std::string input_file_name{argv[1]};
+    // The input file
+    const osmium::io::File input_file{argv[1]};
 
     // Configuration for the multipolygon assembler. We disable the option to
     // create empty areas when invalid multipolygons are encountered. This
@@ -118,21 +123,16 @@ int main(int argc, char* argv[]) {
     osmium::area::Assembler::config_type assembler_config;
     assembler_config.create_empty_areas = false;
 
-    // Initialize the MultipolygonCollector. Its job is to collect all
+    // Initialize the MultipolygonManager. Its job is to collect all
     // relations and member ways needed for each area. It then calls an
     // instance of the osmium::area::Assembler class (with the given config)
     // to actually assemble one area.
-    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector{assembler_config};
+    osmium::area::MultipolygonManager<osmium::area::Assembler> mp_manager{assembler_config};
 
     // We read the input file twice. In the first pass, only relations are
-    // read and fed into the multipolygon collector. The read_meta::no option
-    // disables reading of meta data (such as version numbers, timestamps, etc.)
-    // which are not needed in this case. Disabling this can speed up your
-    // program.
+    // read and fed into the multipolygon manager.
     std::cerr << "Pass 1...\n";
-    osmium::io::Reader reader1{input_file_name, osmium::osm_entity_bits::relation, osmium::io::read_meta::no};
-    collector.read_relations(reader1);
-    reader1.close();
+    osmium::relations::read_relations(input_file, mp_manager);
     std::cerr << "Pass 1 done\n";
 
     // The index storing all node locations.
@@ -151,17 +151,21 @@ int main(int argc, char* argv[]) {
     AmenityHandler data_handler;
 
     // On the second pass we read all objects and run them first through the
-    // node location handler and then the multipolygon collector. The collector
+    // node location handler and then the multipolygon manager. The manager
     // will put the areas it has created into the "buffer" which are then
     // fed through our handler.
+    //
+    // The read_meta::no option disables reading of meta data (such as version
+    // numbers, timestamps, etc.) which are not needed in this case. Disabling
+    // this can speed up your program.
     std::cerr << "Pass 2...\n";
-    osmium::io::Reader reader2{input_file_name, osmium::io::read_meta::no};
+    osmium::io::Reader reader{input_file, osmium::io::read_meta::no};
 
-    osmium::apply(reader2, location_handler, data_handler, collector.handler([&data_handler](const osmium::memory::Buffer& area_buffer) {
+    osmium::apply(reader, location_handler, data_handler, mp_manager.handler([&data_handler](const osmium::memory::Buffer& area_buffer) {
         osmium::apply(area_buffer, data_handler);
     }));
 
-    reader2.close();
+    reader.close();
     std::cerr << "Pass 2 done\n";
 }
 
diff --git a/examples/osmium_area_test.cpp b/examples/osmium_area_test.cpp
index 4bfff72..948c032 100644
--- a/examples/osmium_area_test.cpp
+++ b/examples/osmium_area_test.cpp
@@ -8,7 +8,7 @@
   DEMONSTRATES USE OF:
   * file input
   * location indexes and the NodeLocationsForWays handler
-  * the MultipolygonCollector and Assembler to assemble areas (multipolygons)
+  * the MultipolygonManager and Assembler to assemble areas (multipolygons)
   * your own handler that works with areas (multipolygons)
   * the WKTFactory to write geometries in WKT format
   * the Dump handler
@@ -31,7 +31,7 @@
 
 // For assembling multipolygons
 #include <osmium/area/assembler.hpp>
-#include <osmium/area/multipolygon_collector.hpp>
+#include <osmium/area/multipolygon_manager.hpp>
 
 // For the DynamicHandler class
 #include <osmium/dynamic_handler.hpp>
@@ -52,11 +52,11 @@
 #include <osmium/visitor.hpp>
 
 // For the location index. There are different types of indexes available.
-// This will work for small and medium sized input files.
-#include <osmium/index/map/sparse_mem_array.hpp>
+// This will work for all input files keeping the index in memory.
+#include <osmium/index/map/flex_mem.hpp>
 
 // The type of index used. This must match the include file above
-using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
 
 // The location handler always depends on the index type
 using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
@@ -93,10 +93,10 @@ void print_help() {
 
 int main(int argc, char* argv[]) {
     static struct option long_options[] = {
-        {"help",         no_argument, 0, 'h'},
-        {"dump-wkt",     no_argument, 0, 'w'},
-        {"dump-objects", no_argument, 0, 'o'},
-        {0, 0, 0, 0}
+        {"help",         no_argument, nullptr, 'h'},
+        {"dump-wkt",     no_argument, nullptr, 'w'},
+        {"dump-objects", no_argument, nullptr, 'o'},
+        {nullptr, 0, nullptr, 0}
     };
 
     // Initialize an empty DynamicHandler. Later it will be associated
@@ -107,7 +107,7 @@ int main(int argc, char* argv[]) {
 
     // Read options from command line.
     while (true) {
-        const int c = getopt_long(argc, argv, "hwo", long_options, 0);
+        const int c = getopt_long(argc, argv, "hwo", long_options, nullptr);
         if (c == -1) {
             break;
         }
@@ -139,24 +139,29 @@ int main(int argc, char* argv[]) {
     // are used, but you could change multiple settings.
     osmium::area::Assembler::config_type assembler_config;
 
-    // Initialize the MultipolygonCollector. Its job is to collect all
+    // Set up a filter matching only forests. This will be used to only build
+    // areas with matching tags.
+    osmium::TagsFilter filter{false};
+    filter.add_rule(true, "landuse", "forest");
+    filter.add_rule(true, "natural", "wood");
+
+    // Initialize the MultipolygonManager. Its job is to collect all
     // relations and member ways needed for each area. It then calls an
     // instance of the osmium::area::Assembler class (with the given config)
-    // to actually assemble one area.
-    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector{assembler_config};
+    // to actually assemble one area. The filter parameter is optional, if
+    // it is not set, all areas will be built.
+    osmium::area::MultipolygonManager<osmium::area::Assembler> mp_manager{assembler_config, filter};
 
     // We read the input file twice. In the first pass, only relations are
-    // read and fed into the multipolygon collector.
+    // read and fed into the multipolygon manager.
     std::cerr << "Pass 1...\n";
-    osmium::io::Reader reader1{input_file, osmium::osm_entity_bits::relation};
-    collector.read_relations(reader1);
-    reader1.close();
+    osmium::relations::read_relations(input_file, mp_manager);
     std::cerr << "Pass 1 done\n";
 
     // Output the amount of main memory used so far. All multipolygon relations
     // are in memory now.
     std::cerr << "Memory:\n";
-    collector.used_memory();
+    osmium::relations::print_used_memory(std::cerr, mp_manager.used_memory());
 
     // The index storing all node locations.
     index_type index;
@@ -175,26 +180,29 @@ int main(int argc, char* argv[]) {
     // will put the areas it has created into the "buffer" which are then
     // fed through our "handler".
     std::cerr << "Pass 2...\n";
-    osmium::io::Reader reader2{input_file};
-    osmium::apply(reader2, location_handler, collector.handler([&handler](osmium::memory::Buffer&& buffer) {
+    osmium::io::Reader reader{input_file};
+    osmium::apply(reader, location_handler, mp_manager.handler([&handler](osmium::memory::Buffer&& buffer) {
         osmium::apply(buffer, handler);
     }));
-    reader2.close();
+    reader.close();
     std::cerr << "Pass 2 done\n";
 
     // Output the amount of main memory used so far. All complete multipolygon
     // relations have been cleaned up.
     std::cerr << "Memory:\n";
-    collector.used_memory();
+    osmium::relations::print_used_memory(std::cerr, mp_manager.used_memory());
 
     // If there were multipolgyon relations in the input, but some of their
     // members are not in the input file (which often happens for extracts)
     // this will write the IDs of the incomplete relations to stderr.
-    std::vector<const osmium::Relation*> incomplete_relations = collector.get_incomplete_relations();
-    if (!incomplete_relations.empty()) {
+    std::vector<osmium::object_id_type> incomplete_relations_ids;
+    mp_manager.for_each_incomplete_relation([&](const osmium::relations::RelationHandle& handle){
+        incomplete_relations_ids.push_back(handle->id());
+    });
+    if (!incomplete_relations_ids.empty()) {
         std::cerr << "Warning! Some member ways missing for these multipolygon relations:";
-        for (const auto* relation : incomplete_relations) {
-            std::cerr << " " << relation->id();
+        for (const auto id : incomplete_relations_ids) {
+            std::cerr << " " << id;
         }
         std::cerr << "\n";
     }
diff --git a/examples/osmium_change_tags.cpp b/examples/osmium_change_tags.cpp
index 216d730..ae2e99e 100644
--- a/examples/osmium_change_tags.cpp
+++ b/examples/osmium_change_tags.cpp
@@ -119,7 +119,7 @@ public:
         m_buffer.commit();
     }
 
-    // The way handler is called for each node in the input data.
+    // The way handler is called for each way in the input data.
     void way(const osmium::Way& way) {
         {
             osmium::builder::WayBuilder builder{m_buffer};
@@ -132,7 +132,7 @@ public:
         m_buffer.commit();
     }
 
-    // The relation handler is called for each node in the input data.
+    // The relation handler is called for each relation in the input data.
     void relation(const osmium::Relation& relation) {
         {
             osmium::builder::RelationBuilder builder{m_buffer};
diff --git a/examples/osmium_convert.cpp b/examples/osmium_convert.cpp
index 0ced82c..dfa0abd 100644
--- a/examples/osmium_convert.cpp
+++ b/examples/osmium_convert.cpp
@@ -53,10 +53,10 @@ void print_help() {
 
 int main(int argc, char* argv[]) {
     static struct option long_options[] = {
-        {"help",        no_argument, 0, 'h'},
-        {"from-format", required_argument, 0, 'f'},
-        {"to-format",   required_argument, 0, 't'},
-        {0, 0, 0, 0}
+        {"help",              no_argument, nullptr, 'h'},
+        {"from-format", required_argument, nullptr, 'f'},
+        {"to-format",   required_argument, nullptr, 't'},
+        {nullptr, 0, nullptr, 0}
     };
 
     // Input and output format are empty by default. Later this will mean that
@@ -67,7 +67,7 @@ int main(int argc, char* argv[]) {
 
     // Read options from command line.
     while (true) {
-        const int c = getopt_long(argc, argv, "dhf:t:", long_options, 0);
+        const int c = getopt_long(argc, argv, "dhf:t:", long_options, nullptr);
         if (c == -1) {
             break;
         }
@@ -88,7 +88,7 @@ int main(int argc, char* argv[]) {
     }
 
     const int remaining_args = argc - optind;
-    if (remaining_args > 2) {
+    if (remaining_args == 0 || remaining_args > 2) {
         std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INFILE [OUTFILE]]\n";
         std::exit(1);
     }
@@ -108,8 +108,8 @@ int main(int argc, char* argv[]) {
     // This declares the input and output files using either the suffix of
     // the file names or the format in the 2nd argument. It does not yet open
     // the files.
-    osmium::io::File input_file{input_file_name, input_format};
-    osmium::io::File output_file{output_file_name, output_format};
+    const osmium::io::File input_file{input_file_name, input_format};
+    const osmium::io::File output_file{output_file_name, output_format};
 
     // Input and output files can be OSM data files (without history) or
     // OSM history files. History files are detected if they use the '.osh'
diff --git a/examples/osmium_create_pois.cpp b/examples/osmium_create_pois.cpp
index ff79cbb..df27bfe 100644
--- a/examples/osmium_create_pois.cpp
+++ b/examples/osmium_create_pois.cpp
@@ -46,6 +46,10 @@ int main(int argc, char* argv[]) {
     // Get output file name from command line.
     std::string output_file_name{argv[1]};
 
+    // If output file name is "-", this means STDOUT. Set the OPL file type
+    // in this case. Otherwise take the file type from the file name suffix.
+    osmium::io::File output_file{output_file_name, output_file_name == "-" ? ".opl" : ""};
+
     try {
         // Create a buffer where all objects will live. Use a sensible initial
         // buffer size and set the buffer to automatically grow if needed.
@@ -78,7 +82,7 @@ int main(int argc, char* argv[]) {
 
         // Initialize Writer using the header from above and tell it that it
         // is allowed to overwrite a possibly existing file.
-        osmium::io::Writer writer{output_file_name, header, osmium::io::overwrite::allow};
+        osmium::io::Writer writer{output_file, header, osmium::io::overwrite::allow};
 
         // Write out the contents of the output buffer.
         writer(std::move(buffer));
diff --git a/examples/osmium_debug.cpp b/examples/osmium_debug.cpp
index 8133491..ed68596 100644
--- a/examples/osmium_debug.cpp
+++ b/examples/osmium_debug.cpp
@@ -44,10 +44,18 @@ int main(int argc, char* argv[]) {
     if (argc == 3) {
         read_types = osmium::osm_entity_bits::nothing;
         std::string types = argv[2];
-        if (types.find('n') != std::string::npos) read_types |= osmium::osm_entity_bits::node;
-        if (types.find('w') != std::string::npos) read_types |= osmium::osm_entity_bits::way;
-        if (types.find('r') != std::string::npos) read_types |= osmium::osm_entity_bits::relation;
-        if (types.find('c') != std::string::npos) read_types |= osmium::osm_entity_bits::changeset;
+        if (types.find('n') != std::string::npos) {
+            read_types |= osmium::osm_entity_bits::node;
+        }
+        if (types.find('w') != std::string::npos) {
+            read_types |= osmium::osm_entity_bits::way;
+        }
+        if (types.find('r') != std::string::npos) {
+            read_types |= osmium::osm_entity_bits::relation;
+        }
+        if (types.find('c') != std::string::npos) {
+            read_types |= osmium::osm_entity_bits::changeset;
+        }
     }
 
     // Initialize Reader with file name and the types of entities we want to
diff --git a/examples/osmium_dump_internal.cpp b/examples/osmium_dump_internal.cpp
index e1656ef..58f49b3 100644
--- a/examples/osmium_dump_internal.cpp
+++ b/examples/osmium_dump_internal.cpp
@@ -6,7 +6,7 @@
   indexes to find objects and object relations.
 
   Note that this example programm will only work with small and medium sized
-  OSM file, not with the planet.
+  OSM files, not with the planet.
 
   You can use the osmium_index example program to inspect the indexes.
 
diff --git a/examples/osmium_index_lookup.cpp b/examples/osmium_index_lookup.cpp
index c7f2bb4..4449e70 100644
--- a/examples/osmium_index_lookup.cpp
+++ b/examples/osmium_index_lookup.cpp
@@ -192,17 +192,17 @@ public:
         }
 
         static struct option long_options[] = {
-            {"array",  required_argument, 0, 'a'},
-            {"dump",         no_argument, 0, 'd'},
-            {"help",         no_argument, 0, 'h'},
-            {"list",   required_argument, 0, 'l'},
-            {"search", required_argument, 0, 's'},
-            {"type",   required_argument, 0, 't'},
-            {0, 0, 0, 0}
+            {"array",  required_argument, nullptr, 'a'},
+            {"dump",         no_argument, nullptr, 'd'},
+            {"help",         no_argument, nullptr, 'h'},
+            {"list",   required_argument, nullptr, 'l'},
+            {"search", required_argument, nullptr, 's'},
+            {"type",   required_argument, nullptr, 't'},
+            {nullptr, 0, nullptr, 0}
         };
 
         while (true) {
-            const int c = getopt_long(argc, argv, "a:dhl:s:t:", long_options, 0);
+            const int c = getopt_long(argc, argv, "a:dhl:s:t:", long_options, nullptr);
             if (c == -1) {
                 break;
             }
diff --git a/examples/osmium_location_cache_create.cpp b/examples/osmium_location_cache_create.cpp
index 9de41d1..c01dd97 100644
--- a/examples/osmium_location_cache_create.cpp
+++ b/examples/osmium_location_cache_create.cpp
@@ -68,7 +68,7 @@ int main(int argc, char* argv[]) {
     osmium::io::Reader reader{input_filename, osmium::osm_entity_bits::node};
 
     // Initialize location index on disk creating a new file.
-    const int fd = open(cache_filename.c_str(), O_RDWR | O_CREAT, 0666);
+    const int fd = open(cache_filename.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
     if (fd == -1) {
         std::cerr << "Can not open location cache file '" << cache_filename << "': " << std::strerror(errno) << "\n";
         std::exit(1);
diff --git a/examples/osmium_road_length.cpp b/examples/osmium_road_length.cpp
index 2e1be90..4aced95 100644
--- a/examples/osmium_road_length.cpp
+++ b/examples/osmium_road_length.cpp
@@ -33,14 +33,14 @@
 #include <osmium/visitor.hpp>
 
 // For the location index. There are different types of indexes available.
-// This will work for small and medium sized input files.
-#include <osmium/index/map/sparse_mem_array.hpp>
+// This will work for all input files keeping the index in memory.
+#include <osmium/index/map/flex_mem.hpp>
 
 // For the NodeLocationForWays handler
 #include <osmium/handler/node_locations_for_ways.hpp>
 
 // The type of index used. This must match the include file above
-using index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
 
 // The location handler always depends on the index type
 using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;
diff --git a/include/osmium/area/assembler.hpp b/include/osmium/area/assembler.hpp
index 9e8541a..461a183 100644
--- a/include/osmium/area/assembler.hpp
+++ b/include/osmium/area/assembler.hpp
@@ -33,32 +33,22 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <algorithm>
 #include <cassert>
-#include <cstdint>
-#include <cstdlib>
-#include <cstring>
-#include <functional>
 #include <iostream>
-#include <iterator>
-#include <set>
-#include <string>
-#include <map>
-#include <utility>
 #include <vector>
 
-#include <osmium/area/detail/basic_assembler.hpp>
+#include <osmium/area/assembler_config.hpp>
+#include <osmium/area/detail/basic_assembler_with_tags.hpp>
+#include <osmium/area/detail/segment_list.hpp>
+#include <osmium/area/problem_reporter.hpp>
+#include <osmium/area/stats.hpp>
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/osm/area.hpp>
 #include <osmium/osm/item_type.hpp>
-#include <osmium/osm/location.hpp>
+#include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/tag.hpp>
-#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
-#include <osmium/tags/filter.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
@@ -68,118 +58,7 @@ namespace osmium {
          * Assembles area objects from closed ways or multipolygon relations
          * and their members.
          */
-        class Assembler : public detail::BasicAssembler {
-
-            bool report_ways() const noexcept {
-                if (!config().problem_reporter) {
-                    return false;
-                }
-                return stats().duplicate_nodes ||
-                    stats().duplicate_segments ||
-                    stats().intersections ||
-                    stats().open_rings ||
-                    stats().short_ways ||
-                    stats().touching_rings ||
-                    stats().ways_in_multiple_rings ||
-                    stats().wrong_role;
-            }
-
-            void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const {
-                builder.add_item(way.tags());
-            }
-
-            void add_common_tags(osmium::builder::TagListBuilder& tl_builder, std::set<const osmium::Way*>& ways) const {
-                std::map<std::string, size_t> counter;
-                for (const osmium::Way* way : ways) {
-                    for (const auto& tag : way->tags()) {
-                        std::string kv{tag.key()};
-                        kv.append(1, '\0');
-                        kv.append(tag.value());
-                        ++counter[kv];
-                    }
-                }
-
-                const size_t num_ways = ways.size();
-                for (const auto& t_c : counter) {
-                    if (debug()) {
-                        std::cerr << "        tag " << t_c.first << " is used " << t_c.second << " times in " << num_ways << " ways\n";
-                    }
-                    if (t_c.second == num_ways) {
-                        const size_t len = std::strlen(t_c.first.c_str());
-                        tl_builder.add_tag(t_c.first.c_str(), t_c.first.c_str() + len + 1);
-                    }
-                }
-            }
-
-            struct MPFilter : public osmium::tags::KeyFilter {
-
-                MPFilter() : osmium::tags::KeyFilter(true) {
-                    add(false, "type");
-                    add(false, "created_by");
-                    add(false, "source");
-                    add(false, "note");
-                    add(false, "test:id");
-                    add(false, "test:section");
-                }
-
-            }; // struct MPFilter
-
-            static const MPFilter& filter() noexcept {
-                static const MPFilter filter;
-                return filter;
-            }
-
-            static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) {
-                osmium::builder::TagListBuilder tl_builder{builder};
-                for (const osmium::Tag& tag : tags) {
-                    if (std::strcmp(tag.key(), "type")) {
-                        tl_builder.add_tag(tag.key(), tag.value());
-                    }
-                }
-            }
-
-            void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) {
-                const auto count = std::count_if(relation.tags().cbegin(), relation.tags().cend(), std::cref(filter()));
-
-                if (debug()) {
-                    std::cerr << "  found " << count << " tags on relation (without ignored ones)\n";
-                }
-
-                if (count > 0) {
-                    if (debug()) {
-                        std::cerr << "    use tags from relation\n";
-                    }
-
-                    if (config().keep_type_tag) {
-                        builder.add_item(relation.tags());
-                    } else {
-                        copy_tags_without_type(builder, relation.tags());
-                    }
-                } else {
-                    ++stats().no_tags_on_relation;
-                    if (debug()) {
-                        std::cerr << "    use tags from outer ways\n";
-                    }
-                    std::set<const osmium::Way*> ways;
-                    for (const auto& ring : rings()) {
-                        if (ring.is_outer()) {
-                            ring.get_ways(ways);
-                        }
-                    }
-                    if (ways.size() == 1) {
-                        if (debug()) {
-                            std::cerr << "      only one outer way\n";
-                        }
-                        builder.add_item((*ways.cbegin())->tags());
-                    } else {
-                        if (debug()) {
-                            std::cerr << "      multiple outer ways, get common tags\n";
-                        }
-                        osmium::builder::TagListBuilder tl_builder{builder};
-                        add_common_tags(tl_builder, ways);
-                    }
-                }
-            }
+        class Assembler : public detail::BasicAssemblerWithTags {
 
             bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Way& way) {
                 osmium::builder::AreaBuilder builder{out_buffer};
@@ -187,7 +66,7 @@ namespace osmium {
 
                 const bool area_okay = create_rings();
                 if (area_okay || config().create_empty_areas) {
-                    add_tags_to_area(builder, way);
+                    builder.add_item(way.tags());
                 }
                 if (area_okay) {
                     add_rings_to_area(builder);
@@ -207,7 +86,11 @@ namespace osmium {
 
                 const bool area_okay = create_rings();
                 if (area_okay || config().create_empty_areas) {
-                    add_tags_to_area(builder, relation);
+                    if (config().keep_type_tag) {
+                        builder.add_item(relation.tags());
+                    } else {
+                        copy_tags_without_type(builder, relation.tags());
+                    }
                 }
                 if (area_okay) {
                     add_rings_to_area(builder);
@@ -224,10 +107,8 @@ namespace osmium {
 
         public:
 
-            using config_type = osmium::area::AssemblerConfig;
-
             explicit Assembler(const config_type& config) :
-                detail::BasicAssembler(config) {
+                detail::BasicAssemblerWithTags(config) {
             }
 
             ~Assembler() noexcept = default;
@@ -244,10 +125,6 @@ namespace osmium {
                     return true;
                 }
 
-                if (way.tags().has_tag("area", "no")) {
-                    return true;
-                }
-
                 if (config().problem_reporter) {
                     config().problem_reporter->set_object(osmium::item_type::way, way.id());
                     config().problem_reporter->set_nodes(way.nodes().size());
@@ -296,32 +173,17 @@ namespace osmium {
 
             /**
              * Assemble an area from the given relation and its members.
-             * All members are to be found in the in_buffer at the offsets
-             * given by the members parameter.
-             * The resulting area is put into the out_buffer.
-             *
-             * @deprecated
-             * This function is deprecated. Use the other form of the function
-             * instead.
-             */
-            OSMIUM_DEPRECATED void operator()(const osmium::Relation& relation, const std::vector<size_t>& members, const osmium::memory::Buffer& in_buffer, osmium::memory::Buffer& out_buffer) {
-                std::vector<const osmium::Way*> ways;
-                for (size_t offset : members) {
-                    const osmium::Way& way = in_buffer.get<const osmium::Way>(offset);
-                    ways.push_back(&way);
-                }
-                operator()(relation, ways, out_buffer);
-            }
-
-            /**
-             * Assemble an area from the given relation and its members.
              * The resulting area is put into the out_buffer.
              *
              * @returns false if there was some kind of error building the
              *          area(s), true otherwise.
              */
             bool operator()(const osmium::Relation& relation, const std::vector<const osmium::Way*>& members, osmium::memory::Buffer& out_buffer) {
-                assert(relation.members().size() >= members.size());
+                if (!config().create_new_style_polygons) {
+                    return true;
+                }
+
+                assert(relation.cmembers().size() >= members.size());
 
                 if (config().problem_reporter) {
                     config().problem_reporter->set_object(osmium::item_type::relation, relation.id());
@@ -351,67 +213,15 @@ namespace osmium {
                     std::cerr << "\nAssembling relation " << relation.id() << " containing " << members.size() << " way members with " << segment_list().size() << " nodes\n";
                 }
 
-                const size_t area_offset = out_buffer.committed();
-
                 // Now create the Area object and add the attributes and tags
                 // from the relation.
                 bool okay = create_area(out_buffer, relation, members);
                 if (okay) {
-                    if ((config().create_new_style_polygons && stats().no_tags_on_relation == 0) ||
-                        (config().create_old_style_polygons && stats().no_tags_on_relation != 0)) {
-                        out_buffer.commit();
-                    } else {
-                        out_buffer.rollback();
-                    }
+                    out_buffer.commit();
                 } else {
                     out_buffer.rollback();
                 }
 
-                const osmium::TagList& area_tags = out_buffer.get<osmium::Area>(area_offset).tags(); // tags of the area we just built
-
-                // Find all closed ways that are inner rings and check their
-                // tags. If they are not the same as the tags of the area we
-                // just built, add them to a list and later build areas for
-                // them, too.
-                std::vector<const osmium::Way*> ways_that_should_be_areas;
-                if (stats().wrong_role == 0) {
-                    detail::for_each_member(relation, members, [this, &ways_that_should_be_areas, &area_tags](const osmium::RelationMember& member, const osmium::Way& way) {
-                        if (!std::strcmp(member.role(), "inner")) {
-                            if (!way.nodes().empty() && way.is_closed() && way.tags().size() > 0) {
-                                const auto d = std::count_if(way.tags().cbegin(), way.tags().cend(), std::cref(filter()));
-                                if (d > 0) {
-                                    osmium::tags::KeyFilter::iterator way_fi_begin(std::cref(filter()), way.tags().cbegin(), way.tags().cend());
-                                    osmium::tags::KeyFilter::iterator way_fi_end(std::cref(filter()), way.tags().cend(), way.tags().cend());
-                                    osmium::tags::KeyFilter::iterator area_fi_begin(std::cref(filter()), area_tags.cbegin(), area_tags.cend());
-                                    osmium::tags::KeyFilter::iterator area_fi_end(std::cref(filter()), area_tags.cend(), area_tags.cend());
-
-                                    if (!std::equal(way_fi_begin, way_fi_end, area_fi_begin) || d != std::distance(area_fi_begin, area_fi_end)) {
-                                        ways_that_should_be_areas.push_back(&way);
-                                    } else {
-                                        ++stats().inner_with_same_tags;
-                                        if (config().problem_reporter) {
-                                            config().problem_reporter->report_inner_with_same_tags(way);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    });
-                }
-
-                if (debug()) {
-                    std::cerr << "Done: " << stats() << "\n";
-                }
-
-                // Now build areas for all ways found in the last step.
-                for (const osmium::Way* way : ways_that_should_be_areas) {
-                    Assembler assembler{config()};
-                    if (!assembler(*way, out_buffer)) {
-                        okay = false;
-                    }
-                    stats() += assembler.stats();
-                }
-
                 return okay;
             }
 
diff --git a/include/osmium/area/assembler_config.hpp b/include/osmium/area/assembler_config.hpp
index 2e49080..24bd60a 100644
--- a/include/osmium/area/assembler_config.hpp
+++ b/include/osmium/area/assembler_config.hpp
@@ -51,7 +51,7 @@ namespace osmium {
             /**
              * Optional pointer to problem reporter.
              */
-            osmium::area::ProblemReporter* problem_reporter = nullptr;
+            ProblemReporter* problem_reporter = nullptr;
 
             /**
              * Debug level. If this is greater than zero, debug messages will
@@ -90,7 +90,9 @@ namespace osmium {
 
             /**
              * Create areas for (multi)polygons where the tags are on the
-             * outer way(s).
+             * outer way(s). This is ignored by the area::Assembler which
+             * doesn't support old-style multipolygons any more. Use the
+             * area::AssemblerLegacy if you need this.
              *
              * If this is set to false, those areas will simply be discarded.
              */
@@ -128,7 +130,7 @@ namespace osmium {
              * Constructor
              * @deprecated Use default constructor and set values afterwards.
              */
-            explicit AssemblerConfig(osmium::area::ProblemReporter* pr, bool d = false) :
+            explicit AssemblerConfig(ProblemReporter* pr, bool d = false) :
                 problem_reporter(pr),
                 debug_level(d) {
             }
diff --git a/include/osmium/area/assembler.hpp b/include/osmium/area/assembler_legacy.hpp
similarity index 84%
copy from include/osmium/area/assembler.hpp
copy to include/osmium/area/assembler_legacy.hpp
index 9e8541a..e616c94 100644
--- a/include/osmium/area/assembler.hpp
+++ b/include/osmium/area/assembler_legacy.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_AREA_ASSEMBLER_HPP
-#define OSMIUM_AREA_ASSEMBLER_HPP
+#ifndef OSMIUM_AREA_ASSEMBLER_LEGACY_HPP
+#define OSMIUM_AREA_ASSEMBLER_LEGACY_HPP
 
 /*
 
@@ -35,8 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
-#include <cstdint>
-#include <cstdlib>
 #include <cstring>
 #include <functional>
 #include <iostream>
@@ -47,18 +45,22 @@ DEALINGS IN THE SOFTWARE.
 #include <utility>
 #include <vector>
 
-#include <osmium/area/detail/basic_assembler.hpp>
+#include <osmium/area/assembler_config.hpp>
+#include <osmium/area/detail/basic_assembler_with_tags.hpp>
+#include <osmium/area/detail/proto_ring.hpp>
+#include <osmium/area/detail/segment_list.hpp>
+#include <osmium/area/problem_reporter.hpp>
+#include <osmium/area/stats.hpp>
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/memory/buffer.hpp>
+#include <osmium/memory/collection.hpp>
 #include <osmium/osm/area.hpp>
 #include <osmium/osm/item_type.hpp>
-#include <osmium/osm/location.hpp>
+#include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/tag.hpp>
-#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 #include <osmium/tags/filter.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
@@ -68,28 +70,14 @@ namespace osmium {
          * Assembles area objects from closed ways or multipolygon relations
          * and their members.
          */
-        class Assembler : public detail::BasicAssembler {
-
-            bool report_ways() const noexcept {
-                if (!config().problem_reporter) {
-                    return false;
-                }
-                return stats().duplicate_nodes ||
-                    stats().duplicate_segments ||
-                    stats().intersections ||
-                    stats().open_rings ||
-                    stats().short_ways ||
-                    stats().touching_rings ||
-                    stats().ways_in_multiple_rings ||
-                    stats().wrong_role;
-            }
+        class AssemblerLegacy : public detail::BasicAssemblerWithTags {
 
             void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const {
                 builder.add_item(way.tags());
             }
 
             void add_common_tags(osmium::builder::TagListBuilder& tl_builder, std::set<const osmium::Way*>& ways) const {
-                std::map<std::string, size_t> counter;
+                std::map<std::string, std::size_t> counter;
                 for (const osmium::Way* way : ways) {
                     for (const auto& tag : way->tags()) {
                         std::string kv{tag.key()};
@@ -99,13 +87,13 @@ namespace osmium {
                     }
                 }
 
-                const size_t num_ways = ways.size();
+                const std::size_t num_ways = ways.size();
                 for (const auto& t_c : counter) {
                     if (debug()) {
                         std::cerr << "        tag " << t_c.first << " is used " << t_c.second << " times in " << num_ways << " ways\n";
                     }
                     if (t_c.second == num_ways) {
-                        const size_t len = std::strlen(t_c.first.c_str());
+                        const std::size_t len = std::strlen(t_c.first.c_str());
                         tl_builder.add_tag(t_c.first.c_str(), t_c.first.c_str() + len + 1);
                     }
                 }
@@ -129,15 +117,6 @@ namespace osmium {
                 return filter;
             }
 
-            static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) {
-                osmium::builder::TagListBuilder tl_builder{builder};
-                for (const osmium::Tag& tag : tags) {
-                    if (std::strcmp(tag.key(), "type")) {
-                        tl_builder.add_tag(tag.key(), tag.value());
-                    }
-                }
-            }
-
             void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) {
                 const auto count = std::count_if(relation.tags().cbegin(), relation.tags().cend(), std::cref(filter()));
 
@@ -224,13 +203,11 @@ namespace osmium {
 
         public:
 
-            using config_type = osmium::area::AssemblerConfig;
-
-            explicit Assembler(const config_type& config) :
-                detail::BasicAssembler(config) {
+            explicit AssemblerLegacy(const config_type& config) :
+                detail::BasicAssemblerWithTags(config) {
             }
 
-            ~Assembler() noexcept = default;
+            ~AssemblerLegacy() noexcept = default;
 
             /**
              * Assemble an area from the given way.
@@ -296,25 +273,6 @@ namespace osmium {
 
             /**
              * Assemble an area from the given relation and its members.
-             * All members are to be found in the in_buffer at the offsets
-             * given by the members parameter.
-             * The resulting area is put into the out_buffer.
-             *
-             * @deprecated
-             * This function is deprecated. Use the other form of the function
-             * instead.
-             */
-            OSMIUM_DEPRECATED void operator()(const osmium::Relation& relation, const std::vector<size_t>& members, const osmium::memory::Buffer& in_buffer, osmium::memory::Buffer& out_buffer) {
-                std::vector<const osmium::Way*> ways;
-                for (size_t offset : members) {
-                    const osmium::Way& way = in_buffer.get<const osmium::Way>(offset);
-                    ways.push_back(&way);
-                }
-                operator()(relation, ways, out_buffer);
-            }
-
-            /**
-             * Assemble an area from the given relation and its members.
              * The resulting area is put into the out_buffer.
              *
              * @returns false if there was some kind of error building the
@@ -351,7 +309,7 @@ namespace osmium {
                     std::cerr << "\nAssembling relation " << relation.id() << " containing " << members.size() << " way members with " << segment_list().size() << " nodes\n";
                 }
 
-                const size_t area_offset = out_buffer.committed();
+                const std::size_t area_offset = out_buffer.committed();
 
                 // Now create the Area object and add the attributes and tags
                 // from the relation.
@@ -405,7 +363,7 @@ namespace osmium {
 
                 // Now build areas for all ways found in the last step.
                 for (const osmium::Way* way : ways_that_should_be_areas) {
-                    Assembler assembler{config()};
+                    AssemblerLegacy assembler{config()};
                     if (!assembler(*way, out_buffer)) {
                         okay = false;
                     }
@@ -415,10 +373,10 @@ namespace osmium {
                 return okay;
             }
 
-        }; // class Assembler
+        }; // class AssemblerLegacy
 
     } // namespace area
 
 } // namespace osmium
 
-#endif // OSMIUM_AREA_ASSEMBLER_HPP
+#endif // OSMIUM_AREA_ASSEMBLER_LEGACY_HPP
diff --git a/include/osmium/area/detail/basic_assembler.hpp b/include/osmium/area/detail/basic_assembler.hpp
index 1337611..94168a1 100644
--- a/include/osmium/area/detail/basic_assembler.hpp
+++ b/include/osmium/area/detail/basic_assembler.hpp
@@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE.
 #include <cassert>
 #include <cstdint>
 #include <cstdlib>
-#include <cstring>
 #include <iostream>
 #include <iterator>
 #include <list>
@@ -47,12 +46,8 @@ DEALINGS IN THE SOFTWARE.
 #include <vector>
 
 #include <osmium/builder/osm_object_builder.hpp>
-#include <osmium/memory/buffer.hpp>
-#include <osmium/osm/area.hpp>
-#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
-#include <osmium/osm/relation.hpp>
 #include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 #include <osmium/util/iterator.hpp>
@@ -71,7 +66,7 @@ namespace osmium {
 
         namespace detail {
 
-            using open_ring_its_type = std::list<std::list<detail::ProtoRing>::iterator>;
+            using open_ring_its_type = std::list<std::list<ProtoRing>::iterator>;
 
             struct location_to_ring_map {
                 osmium::Location location;
@@ -90,7 +85,7 @@ namespace osmium {
                     start(false) {
                 }
 
-                const detail::ProtoRing& ring() const noexcept {
+                const ProtoRing& ring() const noexcept {
                     return **ring_it;
                 }
 
@@ -128,17 +123,17 @@ namespace osmium {
                         reverse(r) {
                     }
 
-                    osmium::Location location(const detail::SegmentList& segment_list) const noexcept {
+                    osmium::Location location(const SegmentList& segment_list) const noexcept {
                         const auto& segment = segment_list[item];
                         return reverse ? segment.second().location() : segment.first().location();
                     }
 
-                    const osmium::NodeRef& node_ref(const detail::SegmentList& segment_list) const noexcept {
+                    const osmium::NodeRef& node_ref(const SegmentList& segment_list) const noexcept {
                         const auto& segment = segment_list[item];
                         return reverse ? segment.second() : segment.first();
                     }
 
-                    osmium::Location location(const detail::SegmentList& segment_list, const osmium::Location& default_location) const noexcept {
+                    osmium::Location location(const SegmentList& segment_list, const osmium::Location& default_location) const noexcept {
                         if (item == invalid_item) {
                             return default_location;
                         }
@@ -151,10 +146,10 @@ namespace osmium {
                 const AssemblerConfig& m_config;
 
                 // List of segments (connection between two nodes)
-                osmium::area::detail::SegmentList m_segment_list;
+                SegmentList m_segment_list;
 
                 // The rings we are building from the segments
-                std::list<detail::ProtoRing> m_rings;
+                std::list<ProtoRing> m_rings;
 
                 // All node locations
                 std::vector<slocation> m_locations;
@@ -166,10 +161,10 @@ namespace osmium {
                 area_stats m_stats;
 
                 // The number of members the multipolygon relation has
-                size_t m_num_members = 0;
+                std::size_t m_num_members = 0;
 
                 template <typename TBuilder>
-                static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const detail::ProtoRing& ring) {
+                static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const ProtoRing& ring) {
                     TBuilder ring_builder{builder};
                     ring_builder.add_node_ref(ring.get_node_ref_start());
                     for (const auto& segment : ring.segments()) {
@@ -182,10 +177,10 @@ namespace osmium {
                         std::cerr << "    Checking inner/outer roles\n";
                     }
 
-                    std::unordered_map<const osmium::Way*, const detail::ProtoRing*> way_rings;
+                    std::unordered_map<const osmium::Way*, const ProtoRing*> way_rings;
                     std::unordered_set<const osmium::Way*> ways_in_multiple_rings;
 
-                    for (const detail::ProtoRing& ring : m_rings) {
+                    for (const ProtoRing& ring : m_rings) {
                         for (const auto& segment : ring.segments()) {
                             assert(segment->way());
 
@@ -226,7 +221,7 @@ namespace osmium {
 
                 }
 
-                detail::NodeRefSegment* get_next_segment(const osmium::Location& location) {
+                NodeRefSegment* get_next_segment(const osmium::Location& location) {
                     auto it = std::lower_bound(m_locations.begin(), m_locations.end(), slocation{}, [this, &location](const slocation& lhs, const slocation& rhs) {
                         return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
                     });
@@ -244,11 +239,11 @@ namespace osmium {
                 class rings_stack_element {
 
                     double m_y;
-                    detail::ProtoRing* m_ring_ptr;
+                    ProtoRing* m_ring_ptr;
 
                 public:
 
-                    rings_stack_element(double y, detail::ProtoRing* ring_ptr) :
+                    rings_stack_element(double y, ProtoRing* ring_ptr) :
                         m_y(y),
                         m_ring_ptr(ring_ptr) {
                     }
@@ -257,11 +252,11 @@ namespace osmium {
                         return m_y;
                     }
 
-                    const detail::ProtoRing& ring() const noexcept {
+                    const ProtoRing& ring() const noexcept {
                         return *m_ring_ptr;
                     }
 
-                    detail::ProtoRing* ring_ptr() noexcept {
+                    ProtoRing* ring_ptr() noexcept {
                         return m_ring_ptr;
                     }
 
@@ -273,7 +268,7 @@ namespace osmium {
                         return m_y < rhs.m_y;
                     }
 
-                }; // class ring_stack_element
+                }; // class rings_stack_element
 
                 using rings_stack = std::vector<rings_stack_element>;
 
@@ -287,7 +282,7 @@ namespace osmium {
                     }
                 }
 
-                detail::ProtoRing* find_enclosing_ring(detail::NodeRefSegment* segment) {
+                ProtoRing* find_enclosing_ring(NodeRefSegment* segment) {
                     if (debug()) {
                         std::cerr << "    Looking for ring enclosing " << *segment << "\n";
                     }
@@ -374,30 +369,30 @@ namespace osmium {
                             std::cerr << "    Decided that this is an outer ring\n";
                         }
                         return nullptr;
-                    } else {
-                        if (debug()) {
-                            std::cerr << "    Decided that this is an inner ring\n";
-                        }
-                        assert(!outer_rings.empty());
+                    }
 
-                        std::sort(outer_rings.rbegin(), outer_rings.rend());
-                        if (debug()) {
-                            for (const auto& o : outer_rings) {
-                                std::cerr << "        y=" << o.y() << " " << o.ring() << "\n";
-                            }
-                        }
+                    if (debug()) {
+                        std::cerr << "    Decided that this is an inner ring\n";
+                    }
+                    assert(!outer_rings.empty());
 
-                        remove_duplicates(outer_rings);
-                        if (debug()) {
-                            std::cerr << "      after remove duplicates:\n";
-                            for (const auto& o : outer_rings) {
-                                std::cerr << "        y=" << o.y() << " " << o.ring() << "\n";
-                            }
+                    std::sort(outer_rings.rbegin(), outer_rings.rend());
+                    if (debug()) {
+                        for (const auto& o : outer_rings) {
+                            std::cerr << "        y=" << o.y() << " " << o.ring() << "\n";
                         }
+                    }
 
-                        assert(!outer_rings.empty());
-                        return outer_rings.front().ring_ptr();
+                    remove_duplicates(outer_rings);
+                    if (debug()) {
+                        std::cerr << "      after remove duplicates:\n";
+                        for (const auto& o : outer_rings) {
+                            std::cerr << "        y=" << o.y() << " " << o.ring() << "\n";
+                        }
                     }
+
+                    assert(!outer_rings.empty());
+                    return outer_rings.front().ring_ptr();
                 }
 
                 bool is_split_location(const osmium::Location& location) const noexcept {
@@ -405,7 +400,7 @@ namespace osmium {
                 }
 
                 uint32_t add_new_ring(slocation& node) {
-                    detail::NodeRefSegment* segment = &m_segment_list[node.item];
+                    NodeRefSegment* segment = &m_segment_list[node.item];
                     assert(!segment->is_done());
 
                     if (debug()) {
@@ -416,7 +411,7 @@ namespace osmium {
                         segment->reverse();
                     }
 
-                    detail::ProtoRing* outer_ring = nullptr;
+                    ProtoRing* outer_ring = nullptr;
 
                     if (segment != &m_segment_list.front()) {
                         outer_ring = find_enclosing_ring(segment);
@@ -424,7 +419,7 @@ namespace osmium {
                     segment->mark_direction_done();
 
                     m_rings.emplace_back(segment);
-                    detail::ProtoRing* ring = &m_rings.back();
+                    ProtoRing* ring = &m_rings.back();
                     if (outer_ring) {
                         if (debug()) {
                             std::cerr << "    This is an inner ring. Outer ring is " << *outer_ring << "\n";
@@ -441,7 +436,7 @@ namespace osmium {
                     uint32_t nodes = 1;
                     while (first_location != last_location) {
                         ++nodes;
-                        detail::NodeRefSegment* next_segment = get_next_segment(last_location);
+                        NodeRefSegment* next_segment = get_next_segment(last_location);
                         next_segment->mark_direction_done();
                         if (next_segment->start().location() != last_location) {
                             next_segment->reverse();
@@ -463,7 +458,7 @@ namespace osmium {
                 }
 
                 uint32_t add_new_ring_complex(slocation& node) {
-                    detail::NodeRefSegment* segment = &m_segment_list[node.item];
+                    NodeRefSegment* segment = &m_segment_list[node.item];
                     assert(!segment->is_done());
 
                     if (debug()) {
@@ -475,7 +470,7 @@ namespace osmium {
                     }
 
                     m_rings.emplace_back(segment);
-                    detail::ProtoRing* ring = &m_rings.back();
+                    ProtoRing* ring = &m_rings.back();
 
                     const osmium::Location& first_location = node.location(m_segment_list);
                     osmium::Location last_location = segment->stop().location();
@@ -483,7 +478,7 @@ namespace osmium {
                     uint32_t nodes = 1;
                     while (first_location != last_location && !is_split_location(last_location)) {
                         ++nodes;
-                        detail::NodeRefSegment* next_segment = get_next_segment(last_location);
+                        NodeRefSegment* next_segment = get_next_segment(last_location);
                         if (next_segment->start().location() != last_location) {
                             next_segment->reverse();
                         }
@@ -518,8 +513,8 @@ namespace osmium {
                     });
                 }
 
-                void find_inner_outer_complex(detail::ProtoRing* ring) {
-                    detail::ProtoRing* outer_ring = find_enclosing_ring(ring->min_segment());
+                void find_inner_outer_complex(ProtoRing* ring) {
+                    ProtoRing* outer_ring = find_enclosing_ring(ring->min_segment());
                     if (outer_ring) {
                         outer_ring->add_inner_ring(ring);
                         ring->set_outer_ring(outer_ring);
@@ -532,7 +527,7 @@ namespace osmium {
                     if (debug()) {
                         std::cerr << "  Finding inner/outer rings\n";
                     }
-                    std::vector<detail::ProtoRing*> rings;
+                    std::vector<ProtoRing*> rings;
                     rings.reserve(m_rings.size());
                     for (auto& ring : m_rings) {
                         if (ring.closed()) {
@@ -544,7 +539,7 @@ namespace osmium {
                         return;
                     }
 
-                    std::sort(rings.begin(), rings.end(), [](detail::ProtoRing* a, detail::ProtoRing* b) {
+                    std::sort(rings.begin(), rings.end(), [](ProtoRing* a, ProtoRing* b) {
                         return a->min_segment() < b->min_segment();
                     });
 
@@ -600,7 +595,7 @@ namespace osmium {
                 void create_rings_simple_case() {
                     auto count_remaining = m_segment_list.size();
                     for (slocation& sl : m_locations) {
-                        const detail::NodeRefSegment& segment = m_segment_list[sl.item];
+                        const NodeRefSegment& segment = m_segment_list[sl.item];
                         if (!segment.is_done()) {
                             count_remaining -= add_new_ring(sl);
                             if (count_remaining == 0) {
@@ -628,8 +623,8 @@ namespace osmium {
                 }
 
                 void merge_two_rings(open_ring_its_type& open_ring_its, const location_to_ring_map& m1, const location_to_ring_map& m2) {
-                    std::list<detail::ProtoRing>::iterator r1 = *m1.ring_it;
-                    std::list<detail::ProtoRing>::iterator r2 = *m2.ring_it;
+                    std::list<ProtoRing>::iterator r1 = *m1.ring_it;
+                    std::list<ProtoRing>::iterator r2 = *m2.ring_it;
 
                     if (r1->get_node_ref_stop().location() == r2->get_node_ref_start().location()) {
                         r1->join_forward(*r2);
@@ -687,7 +682,7 @@ namespace osmium {
                 }
 
                 bool there_are_open_rings() const noexcept {
-                    return std::any_of(m_rings.cbegin(), m_rings.cend(), [](const detail::ProtoRing& ring){
+                    return std::any_of(m_rings.cbegin(), m_rings.cend(), [](const ProtoRing& ring){
                         return !ring.closed();
                     });
                 }
@@ -727,9 +722,9 @@ namespace osmium {
                     assert(connections.begin() != connections.end());
 
                     assert(!cand.rings.empty());
-                    const detail::ProtoRing* ring_leading_here = &cand.rings.back().first.ring();
+                    const ProtoRing* ring_leading_here = &cand.rings.back().first.ring();
                     for (const location_to_ring_map& m : connections) {
-                        const detail::ProtoRing& ring = m.ring();
+                        const ProtoRing& ring = m.ring();
 
                         if (&ring != ring_leading_here) {
                             if (debug()) {
@@ -790,7 +785,7 @@ namespace osmium {
                     });
 
                     find_inner_outer_complex();
-                    detail::ProtoRing* outer_ring = find_enclosing_ring(ring_min->ring().min_segment());
+                    ProtoRing* outer_ring = find_enclosing_ring(ring_min->ring().min_segment());
                     bool ring_min_is_outer = !outer_ring;
                     if (debug()) {
                         std::cerr << "  Open ring is " << (ring_min_is_outer ? "outer" : "inner") << " ring\n";
@@ -890,7 +885,7 @@ namespace osmium {
                     // Now find all the rest of the rings (ie not starting at split locations)
                     if (count_remaining > 0) {
                         for (slocation& sl : m_locations) {
-                            const detail::NodeRefSegment& segment = m_segment_list[sl.item];
+                            const NodeRefSegment& segment = m_segment_list[sl.item];
                             if (!segment.is_done()) {
                                 count_remaining -= add_new_ring_complex(sl);
                                 if (count_remaining == 0) {
@@ -918,7 +913,9 @@ namespace osmium {
                             if (debug()) {
                                 std::cerr << "  There are " << open_ring_its.size() << " open rings\n";
                             }
-                            while (try_to_merge(open_ring_its));
+                            while (try_to_merge(open_ring_its)) {
+                                // intentionally left blank
+                            }
 
                             if (!open_ring_its.empty()) {
                                 if (debug()) {
@@ -970,15 +967,15 @@ namespace osmium {
 
             protected:
 
-                const std::list<detail::ProtoRing>& rings() const noexcept {
+                const std::list<ProtoRing>& rings() const noexcept {
                     return m_rings;
                 }
 
-                void set_num_members(size_t size) noexcept {
+                void set_num_members(std::size_t size) noexcept {
                     m_num_members = size;
                 }
 
-                osmium::area::detail::SegmentList& segment_list() noexcept {
+                SegmentList& segment_list() noexcept {
                     return m_segment_list;
                 }
 
@@ -987,10 +984,10 @@ namespace osmium {
                  * area in the buffer.
                  */
                 void add_rings_to_area(osmium::builder::AreaBuilder& builder) const {
-                    for (const detail::ProtoRing& ring : m_rings) {
+                    for (const ProtoRing& ring : m_rings) {
                         if (ring.is_outer()) {
                             build_ring_from_proto_ring<osmium::builder::OuterRingBuilder>(builder, ring);
-                            for (const detail::ProtoRing* inner : ring.inner_rings()) {
+                            for (const ProtoRing* inner : ring.inner_rings()) {
                                 build_ring_from_proto_ring<osmium::builder::InnerRingBuilder>(builder, *inner);
                             }
                         }
@@ -1013,7 +1010,7 @@ namespace osmium {
                     // are two identical segments, they will both be removed. If
                     // there are three, two will be removed and one remains.
                     osmium::Timer timer_dupl;
-                    m_stats.duplicate_segments = m_segment_list.erase_duplicate_segments(m_config.problem_reporter);
+                    m_segment_list.erase_duplicate_segments(m_config.problem_reporter, m_stats.duplicate_segments, m_stats.overlapping_segments);
                     timer_dupl.stop();
 
                     // If there are no segments left at this point, this isn't
@@ -1128,7 +1125,7 @@ namespace osmium {
                         timer_roles.stop();
                     }
 
-                    m_stats.outer_rings = std::count_if(m_rings.cbegin(), m_rings.cend(), [](const detail::ProtoRing& ring){
+                    m_stats.outer_rings = std::count_if(m_rings.cbegin(), m_rings.cend(), [](const ProtoRing& ring){
                         return ring.is_outer();
                     });
                     m_stats.inner_rings = m_rings.size() - m_stats.outer_rings;
diff --git a/include/osmium/area/detail/basic_assembler_with_tags.hpp b/include/osmium/area/detail/basic_assembler_with_tags.hpp
new file mode 100644
index 0000000..1e94680
--- /dev/null
+++ b/include/osmium/area/detail/basic_assembler_with_tags.hpp
@@ -0,0 +1,93 @@
+#ifndef OSMIUM_AREA_DETAIL_BASIC_ASSEMBLER_WITH_TAGS_HPP
+#define OSMIUM_AREA_DETAIL_BASIC_ASSEMBLER_WITH_TAGS_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cstring>
+
+#include <osmium/area/assembler_config.hpp>
+#include <osmium/area/detail/basic_assembler.hpp>
+#include <osmium/area/stats.hpp>
+#include <osmium/builder/osm_object_builder.hpp>
+#include <osmium/osm/tag.hpp>
+
+namespace osmium {
+
+    namespace area {
+
+        namespace detail {
+
+            class BasicAssemblerWithTags : public detail::BasicAssembler {
+
+            protected:
+
+                bool report_ways() const noexcept {
+                    if (!config().problem_reporter) {
+                        return false;
+                    }
+                    return stats().duplicate_nodes ||
+                        stats().duplicate_segments ||
+                        stats().intersections ||
+                        stats().open_rings ||
+                        stats().short_ways ||
+                        stats().touching_rings ||
+                        stats().ways_in_multiple_rings ||
+                        stats().wrong_role;
+                }
+
+                static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) {
+                    osmium::builder::TagListBuilder tl_builder{builder};
+                    for (const osmium::Tag& tag : tags) {
+                        if (std::strcmp(tag.key(), "type")) {
+                            tl_builder.add_tag(tag.key(), tag.value());
+                        }
+                    }
+                }
+
+            public:
+
+                using config_type = osmium::area::AssemblerConfig;
+
+                explicit BasicAssemblerWithTags(const config_type& config) :
+                    BasicAssembler(config) {
+                }
+
+            }; // class BasicAssemblerWithTags
+
+        } // namespace detail
+
+    } // namespace area
+
+} // namespace osmium
+
+#endif // OSMIUM_AREA_DETAIL_BASIC_ASSEMBLER_WITH_TAGS_HPP
diff --git a/include/osmium/area/detail/node_ref_segment.hpp b/include/osmium/area/detail/node_ref_segment.hpp
index 8b37437..8039579 100644
--- a/include/osmium/area/detail/node_ref_segment.hpp
+++ b/include/osmium/area/detail/node_ref_segment.hpp
@@ -262,18 +262,6 @@ namespace osmium {
                 return lhs.first().location() < rhs.first().location();
             }
 
-            inline bool operator>(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept {
-                return rhs < lhs;
-            }
-
-            inline bool operator<=(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept {
-                return ! (rhs < lhs);
-            }
-
-            inline bool operator>=(const NodeRefSegment& lhs, const NodeRefSegment& rhs) noexcept {
-                return ! (lhs < rhs);
-            }
-
             template <typename TChar, typename TTraits>
             inline std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const NodeRefSegment& segment) {
                 return out << segment.start() << "--" << segment.stop()
@@ -283,19 +271,13 @@ namespace osmium {
             }
 
             inline bool outside_x_range(const NodeRefSegment& s1, const NodeRefSegment& s2) noexcept {
-                if (s1.first().location().x() > s2.second().location().x()) {
-                    return true;
-                }
-                return false;
+                return s1.first().location().x() > s2.second().location().x();
             }
 
             inline bool y_range_overlap(const NodeRefSegment& s1, const NodeRefSegment& s2) noexcept {
                 const std::pair<int32_t, int32_t> m1 = std::minmax(s1.first().location().y(), s1.second().location().y());
                 const std::pair<int32_t, int32_t> m2 = std::minmax(s2.first().location().y(), s2.second().location().y());
-                if (m1.first > m2.second || m2.first > m1.second) {
-                    return false;
-                }
-                return true;
+                return !(m1.first > m2.second || m2.first > m1.second);
             }
 
             /**
@@ -331,7 +313,7 @@ namespace osmium {
                 if ((p0 == q0 && p1 == q1) ||
                     (p0 == q1 && p1 == q0)) {
                     // segments are the same
-                    return osmium::Location();
+                    return osmium::Location{};
                 }
 
                 const vec pd = p1 - p0;
@@ -342,7 +324,7 @@ namespace osmium {
 
                     if (p0 == q0 || p0 == q1 || p1 == q0 || p1 == q1) {
                         // touching at an end point
-                        return osmium::Location();
+                        return osmium::Location{};
                     }
 
                     // intersection in a point
@@ -357,10 +339,10 @@ namespace osmium {
                         (d < 0 && na <= 0 && na >= d && nb <= 0 && nb >= d)) {
                         const double ua = double(na) / d;
                         const vec i = p0 + ua * (p1 - p0);
-                        return osmium::Location(int32_t(i.x), int32_t(i.y));
+                        return osmium::Location{int32_t(i.x), int32_t(i.y)};
                     }
 
-                    return osmium::Location();
+                    return osmium::Location{};
                 }
 
                 // segments are collinear
@@ -390,13 +372,12 @@ namespace osmium {
                     if (sl[0].segment != sl[1].segment) {
                         if (sl[0].location == sl[1].location) {
                             return sl[2].location;
-                        } else {
-                            return sl[1].location;
                         }
+                        return sl[1].location;
                     }
                 }
 
-                return osmium::Location();
+                return osmium::Location{};
             }
 
         } // namespace detail
diff --git a/include/osmium/area/detail/segment_list.hpp b/include/osmium/area/detail/segment_list.hpp
index efc45a5..39e16ab 100644
--- a/include/osmium/area/detail/segment_list.hpp
+++ b/include/osmium/area/detail/segment_list.hpp
@@ -49,6 +49,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
+#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 
 namespace osmium {
@@ -90,9 +91,11 @@ namespace osmium {
                 static role_type parse_role(const char* role) noexcept {
                     if (role[0] == '\0') {
                         return role_type::empty;
-                    } else if (!std::strcmp(role, "outer")) {
+                    }
+                    if (!std::strcmp(role, "outer")) {
                         return role_type::outer;
-                    } else if (!std::strcmp(role, "inner")) {
+                    }
+                    if (!std::strcmp(role, "inner")) {
                         return role_type::inner;
                     }
                     return role_type::unknown;
@@ -101,17 +104,16 @@ namespace osmium {
                 /**
                  * Calculate the number of segments in all the ways together.
                  */
-                static size_t get_num_segments(const std::vector<const osmium::Way*>& members) noexcept {
-                    return std::accumulate(members.cbegin(), members.cend(), static_cast<size_t>(0), [](size_t sum, const osmium::Way* way) {
+                static std::size_t get_num_segments(const std::vector<const osmium::Way*>& members) noexcept {
+                    return std::accumulate(members.cbegin(), members.cend(), static_cast<std::size_t>(0), [](std::size_t sum, const osmium::Way* way) {
                         if (way->nodes().empty()) {
                             return sum;
-                        } else {
-                            return sum + way->nodes().size() - 1;
                         }
+                        return sum + way->nodes().size() - 1;
                     });
                 }
 
-                uint32_t extract_segments_from_way_impl(osmium::area::ProblemReporter* problem_reporter, uint64_t& duplicate_nodes, const osmium::Way& way, role_type role) {
+                uint32_t extract_segments_from_way_impl(ProblemReporter* problem_reporter, uint64_t& duplicate_nodes, const osmium::Way& way, role_type role) {
                     uint32_t invalid_locations = 0;
 
                     osmium::NodeRef previous_nr;
@@ -155,7 +157,7 @@ namespace osmium {
                 SegmentList& operator=(SegmentList&&) = delete;
 
                 /// The number of segments in the list.
-                size_t size() const noexcept {
+                std::size_t size() const noexcept {
                     return m_segments.size();
                 }
 
@@ -175,12 +177,12 @@ namespace osmium {
                     return m_segments.back();
                 }
 
-                const NodeRefSegment& operator[](size_t n) const noexcept {
+                const NodeRefSegment& operator[](std::size_t n) const noexcept {
                     assert(n < m_segments.size());
                     return m_segments[n];
                 }
 
-                NodeRefSegment& operator[](size_t n) noexcept {
+                NodeRefSegment& operator[](std::size_t n) noexcept {
                     assert(n < m_segments.size());
                     return m_segments[n];
                 }
@@ -221,7 +223,7 @@ namespace osmium {
                  * same node or different nodes with same location) are
                  * removed after reporting the duplicate node.
                  */
-                uint32_t extract_segments_from_way(osmium::area::ProblemReporter* problem_reporter, uint64_t& duplicate_nodes, const osmium::Way& way) {
+                uint32_t extract_segments_from_way(ProblemReporter* problem_reporter, uint64_t& duplicate_nodes, const osmium::Way& way) {
                     if (way.nodes().empty()) {
                         return 0;
                     }
@@ -233,14 +235,14 @@ namespace osmium {
                  * Extract all segments from all ways that make up this
                  * multipolygon relation and add them to the list.
                  */
-                uint32_t extract_segments_from_ways(osmium::area::ProblemReporter* problem_reporter,
+                uint32_t extract_segments_from_ways(ProblemReporter* problem_reporter,
                                                     uint64_t& duplicate_nodes,
                                                     uint64_t& duplicate_ways,
                                                     const osmium::Relation& relation,
                                                     const std::vector<const osmium::Way*>& members) {
-                    assert(relation.members().size() >= members.size());
+                    assert(relation.cmembers().size() >= members.size());
 
-                    const size_t num_segments = get_num_segments(members);
+                    const std::size_t num_segments = get_num_segments(members);
                     if (problem_reporter) {
                         problem_reporter->set_nodes(num_segments);
                     }
@@ -271,9 +273,7 @@ namespace osmium {
                  * same segment. So if there are three, for instance, two will
                  * be removed and one will be left.
                  */
-                uint32_t erase_duplicate_segments(osmium::area::ProblemReporter* problem_reporter) {
-                    uint32_t duplicate_segments = 0;
-
+                void erase_duplicate_segments(ProblemReporter* problem_reporter, uint64_t& duplicate_segments, uint64_t& overlapping_segments) {
                     while (true) {
                         auto it = std::adjacent_find(m_segments.begin(), m_segments.end());
                         if (it == m_segments.end()) {
@@ -296,10 +296,16 @@ namespace osmium {
                                 problem_reporter->report_duplicate_segment(it->first(), it->second());
                             }
                         }
+
+                        if (it+2 != m_segments.end() && *it == *(it+2)) {
+                            ++overlapping_segments;
+                            if (problem_reporter) {
+                                problem_reporter->report_overlapping_segment(it->first(), it->second());
+                            }
+                        }
+
                         m_segments.erase(it, it+2);
                     }
-
-                    return duplicate_segments;
                 }
 
                 /**
@@ -309,14 +315,14 @@ namespace osmium {
                  *                         reported to this object.
                  * @returns true if there are intersections.
                  */
-                uint32_t find_intersections(osmium::area::ProblemReporter* problem_reporter) const {
+                uint32_t find_intersections(ProblemReporter* problem_reporter) const {
                     if (m_segments.empty()) {
                         return 0;
                     }
 
                     uint32_t found_intersections = 0;
 
-                    for (auto it1 = m_segments.cbegin(); it1 != m_segments.cend()-1; ++it1) {
+                    for (auto it1 = m_segments.cbegin(); it1 != m_segments.cend() - 1; ++it1) {
                         const NodeRefSegment& s1 = *it1;
                         for (auto it2 = it1+1; it2 != m_segments.end(); ++it2) {
                             const NodeRefSegment& s2 = *it2;
@@ -328,7 +334,7 @@ namespace osmium {
                             }
 
                             if (y_range_overlap(s1, s2)) {
-                                osmium::Location intersection = calculate_intersection(s1, s2);
+                                osmium::Location intersection{calculate_intersection(s1, s2)};
                                 if (intersection) {
                                     ++found_intersections;
                                     if (m_debug) {
diff --git a/include/osmium/area/geom_assembler.hpp b/include/osmium/area/geom_assembler.hpp
index 84870ae..8fb3b3a 100644
--- a/include/osmium/area/geom_assembler.hpp
+++ b/include/osmium/area/geom_assembler.hpp
@@ -33,12 +33,13 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <osmium/area/assembler_config.hpp>
 #include <osmium/area/detail/basic_assembler.hpp>
+#include <osmium/area/detail/segment_list.hpp>
+#include <osmium/area/stats.hpp>
 #include <osmium/builder/osm_object_builder.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/relation.hpp>
-#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
 
 namespace osmium {
diff --git a/include/osmium/area/multipolygon_collector.hpp b/include/osmium/area/multipolygon_collector.hpp
index ad2e56d..9ad68e1 100644
--- a/include/osmium/area/multipolygon_collector.hpp
+++ b/include/osmium/area/multipolygon_collector.hpp
@@ -81,7 +81,7 @@ namespace osmium {
 
             osmium::memory::Buffer m_output_buffer;
 
-            osmium::area::area_stats m_stats;
+            area_stats m_stats;
 
             static constexpr size_t initial_output_buffer_size = 1024 * 1024;
             static constexpr size_t max_buffer_size_for_flush = 100 * 1024;
@@ -109,7 +109,7 @@ namespace osmium {
                 m_output_buffer(initial_output_buffer_size, osmium::memory::Buffer::auto_grow::yes) {
             }
 
-            const osmium::area::area_stats& stats() const noexcept {
+            const area_stats& stats() const noexcept {
                 return m_stats;
             }
 
@@ -127,11 +127,7 @@ namespace osmium {
                     return false;
                 }
 
-                if ((!std::strcmp(type, "multipolygon")) || (!std::strcmp(type, "boundary"))) {
-                    return true;
-                }
-
-                return false;
+                return (!std::strcmp(type, "multipolygon")) || (!std::strcmp(type, "boundary"));
             }
 
             /**
@@ -155,11 +151,11 @@ namespace osmium {
                 }
                 try {
                     if (!way.nodes().front().location() || !way.nodes().back().location()) {
-                        throw osmium::invalid_location("invalid location");
+                        throw osmium::invalid_location{"invalid location"};
                     }
                     if (way.ends_have_same_location()) {
                         // way is closed and has enough nodes, build simple multipolygon
-                        TAssembler assembler(m_assembler_config);
+                        TAssembler assembler{m_assembler_config};
                         assembler(way, m_output_buffer);
                         m_stats += assembler.stats();
                         possibly_flush_output_buffer();
@@ -183,7 +179,7 @@ namespace osmium {
                 }
 
                 try {
-                    TAssembler assembler(m_assembler_config);
+                    TAssembler assembler{m_assembler_config};
                     assembler(relation, ways, m_output_buffer);
                     m_stats += assembler.stats();
                     possibly_flush_output_buffer();
diff --git a/include/osmium/area/multipolygon_collector.hpp b/include/osmium/area/multipolygon_manager.hpp
similarity index 51%
copy from include/osmium/area/multipolygon_collector.hpp
copy to include/osmium/area/multipolygon_manager.hpp
index ad2e56d..e5ee2c8 100644
--- a/include/osmium/area/multipolygon_collector.hpp
+++ b/include/osmium/area/multipolygon_manager.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_AREA_MULTIPOLYGON_COLLECTOR_HPP
-#define OSMIUM_AREA_MULTIPOLYGON_COLLECTOR_HPP
+#ifndef OSMIUM_AREA_MULTIPOLYGON_MANAGER_HPP
+#define OSMIUM_AREA_MULTIPOLYGON_MANAGER_HPP
 
 /*
 
@@ -34,26 +34,27 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
+#include <cassert>
 #include <cstddef>
+#include <cstdint>
 #include <cstring>
 #include <vector>
 
 #include <osmium/area/stats.hpp>
-#include <osmium/memory/buffer.hpp>
 #include <osmium/osm/item_type.hpp>
-#include <osmium/osm/location.hpp>
-#include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/way.hpp>
-#include <osmium/relations/collector.hpp>
+#include <osmium/relations/manager_util.hpp>
+#include <osmium/relations/members_database.hpp>
+#include <osmium/relations/relations_database.hpp>
+#include <osmium/relations/relations_manager.hpp>
+#include <osmium/storage/item_stash.hpp>
+#include <osmium/tags/taglist.hpp>
+#include <osmium/tags/tags_filter.hpp>
 
 namespace osmium {
 
-    namespace relations {
-        class RelationMeta;
-    } // namespace relations
-
     /**
      * @brief Code related to the building of areas (multipolygons) from relations.
      */
@@ -72,143 +73,117 @@ namespace osmium {
          * @pre The Ids of all objects must be unique in the input data.
          */
         template <typename TAssembler>
-        class MultipolygonCollector : public osmium::relations::Collector<MultipolygonCollector<TAssembler>, false, true, false> {
-
-            using collector_type = osmium::relations::Collector<MultipolygonCollector<TAssembler>, false, true, false>;
+        class MultipolygonManager : public osmium::relations::RelationsManager<MultipolygonManager<TAssembler>, false, true, false> {
 
             using assembler_config_type = typename TAssembler::config_type;
             const assembler_config_type m_assembler_config;
 
-            osmium::memory::Buffer m_output_buffer;
+            area_stats m_stats;
 
-            osmium::area::area_stats m_stats;
-
-            static constexpr size_t initial_output_buffer_size = 1024 * 1024;
-            static constexpr size_t max_buffer_size_for_flush = 100 * 1024;
-
-            void flush_output_buffer() {
-                if (this->callback()) {
-                    osmium::memory::Buffer buffer{initial_output_buffer_size};
-                    using std::swap;
-                    swap(buffer, m_output_buffer);
-                    this->callback()(std::move(buffer));
-                }
-            }
-
-            void possibly_flush_output_buffer() {
-                if (m_output_buffer.committed() > max_buffer_size_for_flush) {
-                    flush_output_buffer();
-                }
-            }
+            osmium::TagsFilter m_filter;
 
         public:
 
-            explicit MultipolygonCollector(const assembler_config_type& assembler_config) :
-                collector_type(),
+            /**
+             * Construct a MultipolygonManager.
+             *
+             * @param assembler_config The configuration that will be given to
+             *                         any newly constructed area assembler.
+             * @param filter An optional filter specifying what tags are
+             *               needed on closed ways or multipolygon relations
+             *               to build the area.
+             */
+            explicit MultipolygonManager(const assembler_config_type& assembler_config, const osmium::TagsFilter& filter = osmium::TagsFilter{true}) :
                 m_assembler_config(assembler_config),
-                m_output_buffer(initial_output_buffer_size, osmium::memory::Buffer::auto_grow::yes) {
+                m_filter(filter) {
             }
 
-            const osmium::area::area_stats& stats() const noexcept {
+            /**
+             * Access the aggregated statistics generated by the assemblers
+             * called from the manager.
+             */
+            const area_stats& stats() const noexcept {
                 return m_stats;
             }
 
             /**
              * We are interested in all relations tagged with type=multipolygon
-             * or type=boundary.
-             *
-             * Overwritten from the base class.
+             * or type=boundary with at least one way member.
              */
-            bool keep_relation(const osmium::Relation& relation) const {
+            bool new_relation(const osmium::Relation& relation) const {
                 const char* type = relation.tags().get_value_by_key("type");
 
                 // ignore relations without "type" tag
-                if (!type) {
+                if (type == nullptr) {
                     return false;
                 }
 
-                if ((!std::strcmp(type, "multipolygon")) || (!std::strcmp(type, "boundary"))) {
-                    return true;
+                if (((!std::strcmp(type, "multipolygon")) || (!std::strcmp(type, "boundary"))) && osmium::tags::match_any_of(relation.tags(), m_filter)) {
+                    return std::any_of(relation.members().cbegin(), relation.members().cend(), [](const RelationMember& member) {
+                        return member.type() == osmium::item_type::way;
+                    });
                 }
 
                 return false;
             }
 
             /**
-             * Overwritten from the base class.
-             */
-            bool keep_member(const osmium::relations::RelationMeta& /*relation_meta*/, const osmium::RelationMember& member) const {
-                // We are only interested in members of type way.
-                return member.type() == osmium::item_type::way;
-            }
-
-            /**
-             * This is called when a way is not in any multipolygon
-             * relation.
-             *
-             * Overwritten from the base class.
+             * This is called when a relation is complete, ie. all members
+             * were found in the input. It will build the area using the
+             * assembler.
              */
-            void way_not_in_any_relation(const osmium::Way& way) {
-                // you need at least 4 nodes to make up a polygon
-                if (way.nodes().size() <= 3) {
-                    return;
-                }
-                try {
-                    if (!way.nodes().front().location() || !way.nodes().back().location()) {
-                        throw osmium::invalid_location("invalid location");
-                    }
-                    if (way.ends_have_same_location()) {
-                        // way is closed and has enough nodes, build simple multipolygon
-                        TAssembler assembler(m_assembler_config);
-                        assembler(way, m_output_buffer);
-                        m_stats += assembler.stats();
-                        possibly_flush_output_buffer();
-                    }
-                } catch (const osmium::invalid_location&) {
-                    // XXX ignore
-                }
-            }
-
-            void complete_relation(osmium::relations::RelationMeta& relation_meta) {
-                const osmium::Relation& relation = this->get_relation(relation_meta);
-                const osmium::memory::Buffer& buffer = this->members_buffer();
-
+            void complete_relation(const osmium::Relation& relation) {
                 std::vector<const osmium::Way*> ways;
                 ways.reserve(relation.members().size());
                 for (const auto& member : relation.members()) {
                     if (member.ref() != 0) {
-                        const size_t offset = this->get_offset(member.type(), member.ref());
-                        ways.push_back(&buffer.get<const osmium::Way>(offset));
+                        ways.push_back(this->get_member_way(member.ref()));
+                        assert(ways.back() != nullptr);
                     }
                 }
 
                 try {
-                    TAssembler assembler(m_assembler_config);
-                    assembler(relation, ways, m_output_buffer);
+                    TAssembler assembler{m_assembler_config};
+                    assembler(relation, ways, this->buffer());
                     m_stats += assembler.stats();
-                    possibly_flush_output_buffer();
                 } catch (const osmium::invalid_location&) {
                     // XXX ignore
                 }
             }
 
-            void flush() {
-                flush_output_buffer();
-            }
+            void after_way(const osmium::Way& way) {
+                // you need at least 4 nodes to make up a polygon
+                if (way.nodes().size() <= 3) {
+                    return;
+                }
 
-            osmium::memory::Buffer read() {
-                osmium::memory::Buffer buffer{initial_output_buffer_size, osmium::memory::Buffer::auto_grow::yes};
+                try {
+                    if (!way.nodes().front().location() || !way.nodes().back().location()) {
+                        throw osmium::invalid_location{"invalid location"};
+                    }
+                    if (way.ends_have_same_location()) {
+                        if (way.tags().has_tag("area", "no")) {
+                            return;
+                        }
 
-                using std::swap;
-                swap(buffer, m_output_buffer);
+                        if (osmium::tags::match_none_of(way.tags(), m_filter)) {
+                            return;
+                        }
 
-                return buffer;
+                        TAssembler assembler{m_assembler_config};
+                        assembler(way, this->buffer());
+                        m_stats += assembler.stats();
+                        this->possibly_flush();
+                    }
+                } catch (const osmium::invalid_location&) {
+                    // XXX ignore
+                }
             }
 
-        }; // class MultipolygonCollector
+        }; // class MultipolygonManager
 
     } // namespace area
 
 } // namespace osmium
 
-#endif // OSMIUM_AREA_MULTIPOLYGON_COLLECTOR_HPP
+#endif // OSMIUM_AREA_MULTIPOLYGON_MANAGER_HPP
diff --git a/include/osmium/area/multipolygon_collector.hpp b/include/osmium/area/multipolygon_manager_legacy.hpp
similarity index 56%
copy from include/osmium/area/multipolygon_collector.hpp
copy to include/osmium/area/multipolygon_manager_legacy.hpp
index ad2e56d..491557a 100644
--- a/include/osmium/area/multipolygon_collector.hpp
+++ b/include/osmium/area/multipolygon_manager_legacy.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_AREA_MULTIPOLYGON_COLLECTOR_HPP
-#define OSMIUM_AREA_MULTIPOLYGON_COLLECTOR_HPP
+#ifndef OSMIUM_AREA_MULTIPOLYGON_MANAGER_LEGACY_HPP
+#define OSMIUM_AREA_MULTIPOLYGON_MANAGER_LEGACY_HPP
 
 /*
 
@@ -34,26 +34,29 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <algorithm>
+#include <cassert>
 #include <cstddef>
+#include <cstdint>
 #include <cstring>
 #include <vector>
 
 #include <osmium/area/stats.hpp>
+#include <osmium/handler.hpp>
+#include <osmium/handler/check_order.hpp>
 #include <osmium/memory/buffer.hpp>
+#include <osmium/memory/callback_buffer.hpp>
 #include <osmium/osm/item_type.hpp>
-#include <osmium/osm/location.hpp>
-#include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/way.hpp>
-#include <osmium/relations/collector.hpp>
+#include <osmium/relations/manager_util.hpp>
+#include <osmium/relations/members_database.hpp>
+#include <osmium/relations/relations_database.hpp>
+#include <osmium/relations/relations_manager.hpp>
+#include <osmium/storage/item_stash.hpp>
 
 namespace osmium {
 
-    namespace relations {
-        class RelationMeta;
-    } // namespace relations
-
     /**
      * @brief Code related to the building of areas (multipolygons) from relations.
      */
@@ -72,54 +75,38 @@ namespace osmium {
          * @pre The Ids of all objects must be unique in the input data.
          */
         template <typename TAssembler>
-        class MultipolygonCollector : public osmium::relations::Collector<MultipolygonCollector<TAssembler>, false, true, false> {
-
-            using collector_type = osmium::relations::Collector<MultipolygonCollector<TAssembler>, false, true, false>;
+        class MultipolygonManagerLegacy : public osmium::relations::RelationsManager<MultipolygonManagerLegacy<TAssembler>, false, true, false> {
 
             using assembler_config_type = typename TAssembler::config_type;
             const assembler_config_type m_assembler_config;
 
-            osmium::memory::Buffer m_output_buffer;
-
-            osmium::area::area_stats m_stats;
-
-            static constexpr size_t initial_output_buffer_size = 1024 * 1024;
-            static constexpr size_t max_buffer_size_for_flush = 100 * 1024;
-
-            void flush_output_buffer() {
-                if (this->callback()) {
-                    osmium::memory::Buffer buffer{initial_output_buffer_size};
-                    using std::swap;
-                    swap(buffer, m_output_buffer);
-                    this->callback()(std::move(buffer));
-                }
-            }
-
-            void possibly_flush_output_buffer() {
-                if (m_output_buffer.committed() > max_buffer_size_for_flush) {
-                    flush_output_buffer();
-                }
-            }
+            area_stats m_stats;
 
         public:
 
-            explicit MultipolygonCollector(const assembler_config_type& assembler_config) :
-                collector_type(),
-                m_assembler_config(assembler_config),
-                m_output_buffer(initial_output_buffer_size, osmium::memory::Buffer::auto_grow::yes) {
+            /**
+             * Construct a MultipolygonManagerLegacy.
+             *
+             * @param assembler_config The configuration that will be given to
+             *                         any newly constructed area assembler.
+             */
+            explicit MultipolygonManagerLegacy(const assembler_config_type& assembler_config) :
+                m_assembler_config(assembler_config) {
             }
 
-            const osmium::area::area_stats& stats() const noexcept {
+            /**
+             * Access the aggregated statistics generated by the assemblers
+             * called from the manager.
+             */
+            const area_stats& stats() const noexcept {
                 return m_stats;
             }
 
             /**
              * We are interested in all relations tagged with type=multipolygon
-             * or type=boundary.
-             *
-             * Overwritten from the base class.
+             * or type=boundary with at least one way member.
              */
-            bool keep_relation(const osmium::Relation& relation) const {
+            bool new_relation(const osmium::Relation& relation) const {
                 const char* type = relation.tags().get_value_by_key("type");
 
                 // ignore relations without "type" tag
@@ -128,25 +115,41 @@ namespace osmium {
                 }
 
                 if ((!std::strcmp(type, "multipolygon")) || (!std::strcmp(type, "boundary"))) {
-                    return true;
+                    return std::any_of(relation.members().cbegin(), relation.members().cend(), [](const RelationMember& member) {
+                        return member.type() == osmium::item_type::way;
+                    });
                 }
 
                 return false;
             }
 
             /**
-             * Overwritten from the base class.
+             * This is called when a relation is complete, ie. all members
+             * were found in the input. It will build the area using the
+             * assembler.
              */
-            bool keep_member(const osmium::relations::RelationMeta& /*relation_meta*/, const osmium::RelationMember& member) const {
-                // We are only interested in members of type way.
-                return member.type() == osmium::item_type::way;
+            void complete_relation(const osmium::Relation& relation) {
+                std::vector<const osmium::Way*> ways;
+                ways.reserve(relation.members().size());
+                for (const auto& member : relation.members()) {
+                    if (member.ref() != 0) {
+                        ways.push_back(this->get_member_way(member.ref()));
+                        assert(ways.back() != nullptr);
+                    }
+                }
+
+                try {
+                    TAssembler assembler{m_assembler_config};
+                    assembler(relation, ways, this->buffer());
+                    m_stats += assembler.stats();
+                } catch (const osmium::invalid_location&) {
+                    // XXX ignore
+                }
             }
 
             /**
              * This is called when a way is not in any multipolygon
              * relation.
-             *
-             * Overwritten from the base class.
              */
             void way_not_in_any_relation(const osmium::Way& way) {
                 // you need at least 4 nodes to make up a polygon
@@ -155,60 +158,23 @@ namespace osmium {
                 }
                 try {
                     if (!way.nodes().front().location() || !way.nodes().back().location()) {
-                        throw osmium::invalid_location("invalid location");
+                        throw osmium::invalid_location{"invalid location"};
                     }
                     if (way.ends_have_same_location()) {
                         // way is closed and has enough nodes, build simple multipolygon
-                        TAssembler assembler(m_assembler_config);
-                        assembler(way, m_output_buffer);
+                        TAssembler assembler{m_assembler_config};
+                        assembler(way, this->buffer());
                         m_stats += assembler.stats();
-                        possibly_flush_output_buffer();
                     }
                 } catch (const osmium::invalid_location&) {
                     // XXX ignore
                 }
             }
 
-            void complete_relation(osmium::relations::RelationMeta& relation_meta) {
-                const osmium::Relation& relation = this->get_relation(relation_meta);
-                const osmium::memory::Buffer& buffer = this->members_buffer();
-
-                std::vector<const osmium::Way*> ways;
-                ways.reserve(relation.members().size());
-                for (const auto& member : relation.members()) {
-                    if (member.ref() != 0) {
-                        const size_t offset = this->get_offset(member.type(), member.ref());
-                        ways.push_back(&buffer.get<const osmium::Way>(offset));
-                    }
-                }
-
-                try {
-                    TAssembler assembler(m_assembler_config);
-                    assembler(relation, ways, m_output_buffer);
-                    m_stats += assembler.stats();
-                    possibly_flush_output_buffer();
-                } catch (const osmium::invalid_location&) {
-                    // XXX ignore
-                }
-            }
-
-            void flush() {
-                flush_output_buffer();
-            }
-
-            osmium::memory::Buffer read() {
-                osmium::memory::Buffer buffer{initial_output_buffer_size, osmium::memory::Buffer::auto_grow::yes};
-
-                using std::swap;
-                swap(buffer, m_output_buffer);
-
-                return buffer;
-            }
-
-        }; // class MultipolygonCollector
+        }; // class MultipolygonManagerLegacy
 
     } // namespace area
 
 } // namespace osmium
 
-#endif // OSMIUM_AREA_MULTIPOLYGON_COLLECTOR_HPP
+#endif // OSMIUM_AREA_MULTIPOLYGON_MANAGER_LEGACY_HPP
diff --git a/include/osmium/area/problem_reporter.hpp b/include/osmium/area/problem_reporter.hpp
index 797845b..181d2d1 100644
--- a/include/osmium/area/problem_reporter.hpp
+++ b/include/osmium/area/problem_reporter.hpp
@@ -144,6 +144,18 @@ namespace osmium {
             }
 
             /**
+             * Report a duplicate segments. Two or more segments are directly
+             * on top of each other. This can be a problem, if there is a
+             * spike for instance, or it could be okay, if there are touching
+             * inner rings.
+             *
+             * @param nr1  NodeRef of one end of the segment.
+             * @param nr2  NodeRef of the other end of the segment.
+             */
+            virtual void report_overlapping_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) {
+            }
+
+            /**
              * Report an open ring.
              *
              * @param nr   NodeRef of one end of the ring.
diff --git a/include/osmium/area/problem_reporter_exception.hpp b/include/osmium/area/problem_reporter_exception.hpp
index f1d64d8..cfc2b54 100644
--- a/include/osmium/area/problem_reporter_exception.hpp
+++ b/include/osmium/area/problem_reporter_exception.hpp
@@ -84,6 +84,12 @@ namespace osmium {
                 throw std::runtime_error{m_sstream.str()};
             }
 
+            void report_overlapping_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) override {
+                m_sstream.str("");
+                ProblemReporterStream::report_overlapping_segment(nr1, nr2);
+                throw std::runtime_error{m_sstream.str()};
+            }
+
             void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) override {
                 m_sstream.str("");
                 ProblemReporterStream::report_ring_not_closed(nr, way);
diff --git a/include/osmium/area/problem_reporter_ogr.hpp b/include/osmium/area/problem_reporter_ogr.hpp
index 80cbbc2..5f531c8 100644
--- a/include/osmium/area/problem_reporter_ogr.hpp
+++ b/include/osmium/area/problem_reporter_ogr.hpp
@@ -158,6 +158,10 @@ namespace osmium {
                 write_line("duplicate_segment", nr1.ref(), nr2.ref(), nr1.location(), nr2.location());
             }
 
+            void report_overlapping_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) override {
+                write_line("overlapping_segment", nr1.ref(), nr2.ref(), nr1.location(), nr2.location());
+            }
+
             void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) override {
                 write_point("ring_not_closed", nr.ref(), way ? way->id() : 0, nr.location());
             }
diff --git a/include/osmium/area/problem_reporter_stream.hpp b/include/osmium/area/problem_reporter_stream.hpp
index ee414a6..774e9c3 100644
--- a/include/osmium/area/problem_reporter_stream.hpp
+++ b/include/osmium/area/problem_reporter_stream.hpp
@@ -85,6 +85,12 @@ namespace osmium {
                        << " node_id2=" << nr2.ref() << " location2=" << nr2.location() << "\n";
             }
 
+            void report_overlapping_segment(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) override {
+                header("overlapping segment");
+                *m_out << "node_id1=" << nr1.ref() << " location1=" << nr1.location()
+                       << " node_id2=" << nr2.ref() << " location2=" << nr2.location() << "\n";
+            }
+
             void report_ring_not_closed(const osmium::NodeRef& nr, const osmium::Way* way = nullptr) override {
                 header("ring not closed");
                 *m_out << "node_id=" << nr.ref() << " location=" << nr.location();
diff --git a/include/osmium/area/stats.hpp b/include/osmium/area/stats.hpp
index 2fc18b7..6133da8 100644
--- a/include/osmium/area/stats.hpp
+++ b/include/osmium/area/stats.hpp
@@ -64,6 +64,7 @@ namespace osmium {
             uint64_t nodes = 0; ///< Number of nodes in the area
             uint64_t open_rings = 0; ///< Number of open rings in the area
             uint64_t outer_rings = 0; ///< Number of outer rings in the area
+            uint64_t overlapping_segments = 0; ///< Three or more segments with same end points
             uint64_t short_ways = 0; ///< Number of ways with less than two nodes
             uint64_t single_way_in_mp_relation = 0; ///< Multipolygon relation containing a single way
             uint64_t touching_rings = 0; ///< Rings touching in a node
diff --git a/include/osmium/builder/builder.hpp b/include/osmium/builder/builder.hpp
index d435884..4351588 100644
--- a/include/osmium/builder/builder.hpp
+++ b/include/osmium/builder/builder.hpp
@@ -35,16 +35,12 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
+#include <cstddef>
 #include <cstdint>
 #include <cstring>
-#include <new>
-#include <string>
-#include <type_traits>
 
 #include <osmium/memory/buffer.hpp>
 #include <osmium/memory/item.hpp>
-#include <osmium/osm/types.hpp>
-#include <osmium/util/cast.hpp>
 #include <osmium/util/compatibility.hpp>
 
 namespace osmium {
@@ -62,7 +58,7 @@ namespace osmium {
 
             osmium::memory::Buffer& m_buffer;
             Builder* m_parent;
-            size_t m_item_offset;
+            std::size_t m_item_offset;
 
             Builder(const Builder&) = delete;
             Builder(Builder&&) = delete;
@@ -101,7 +97,7 @@ namespace osmium {
                 return *reinterpret_cast<osmium::memory::Item*>(m_buffer.data() + m_item_offset);
             }
 
-            unsigned char* reserve_space(size_t size) {
+            unsigned char* reserve_space(std::size_t size) {
                 return m_buffer.reserve_space(size);
             }
 
@@ -119,7 +115,9 @@ namespace osmium {
              *
              */
             void add_padding(bool self = false) {
-                const auto padding = osmium::memory::align_bytes - (size() % osmium::memory::align_bytes);
+                // We know the padding is only a very small number, so it will
+                // always fit.
+                const auto padding = static_cast<osmium::memory::item_size_type>(osmium::memory::align_bytes - (size() % osmium::memory::align_bytes));
                 if (padding != osmium::memory::align_bytes) {
                     std::fill_n(reserve_space(padding), padding, 0);
                     if (self) {
@@ -131,7 +129,7 @@ namespace osmium {
                 }
             }
 
-            void add_size(uint32_t size) {
+            void add_size(osmium::memory::item_size_type size) {
                 item().add_size(size);
                 if (m_parent) {
                     m_parent->add_size(size);
diff --git a/include/osmium/builder/osm_object_builder.hpp b/include/osmium/builder/osm_object_builder.hpp
index d4483b1..4706314 100644
--- a/include/osmium/builder/osm_object_builder.hpp
+++ b/include/osmium/builder/osm_object_builder.hpp
@@ -33,7 +33,10 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <algorithm>
 #include <cassert>
+#include <cstdint>
+#include <cstddef>
 #include <cstring>
 #include <initializer_list>
 #include <limits>
@@ -43,25 +46,25 @@ DEALINGS IN THE SOFTWARE.
 #include <utility>
 
 #include <osmium/builder/builder.hpp>
+#include <osmium/memory/item.hpp>
+#include <osmium/osm/area.hpp>
+#include <osmium/osm/box.hpp>
+#include <osmium/osm/changeset.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/node.hpp>
 #include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/object.hpp>
-#include <osmium/osm/tag.hpp>
-#include <osmium/osm/types.hpp>
-#include <osmium/memory/item.hpp>
-#include <osmium/osm/area.hpp>
-#include <osmium/osm/changeset.hpp>
 #include <osmium/osm/relation.hpp>
+#include <osmium/osm/tag.hpp>
 #include <osmium/osm/timestamp.hpp>
+#include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
+#include <osmium/util/cast.hpp>
 #include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
-    class Node;
-
     namespace memory {
         class Buffer;
     } // namespace memory
@@ -74,12 +77,12 @@ namespace osmium {
 
             explicit TagListBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
                 Builder(buffer, parent, sizeof(TagList)) {
-                new (&item()) TagList();
+                new (&item()) TagList{};
             }
 
             explicit TagListBuilder(Builder& parent) :
                 Builder(parent.buffer(), &parent, sizeof(TagList)) {
-                new (&item()) TagList();
+                new (&item()) TagList{};
             }
 
             ~TagListBuilder() {
@@ -94,10 +97,10 @@ namespace osmium {
              */
             void add_tag(const char* key, const char* value) {
                 if (std::strlen(key) > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM tag key is too long");
+                    throw std::length_error{"OSM tag key is too long"};
                 }
                 if (std::strlen(value) > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM tag value is too long");
+                    throw std::length_error{"OSM tag value is too long"};
                 }
                 add_size(append(key));
                 add_size(append(value));
@@ -111,12 +114,12 @@ namespace osmium {
              * @param value Pointer to tag value.
              * @param value_length Length of value (not including the \0 byte).
              */
-            void add_tag(const char* key, const size_t key_length, const char* value, const size_t value_length) {
+            void add_tag(const char* key, const std::size_t key_length, const char* value, const std::size_t value_length) {
                 if (key_length > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM tag key is too long");
+                    throw std::length_error{"OSM tag key is too long"};
                 }
                 if (value_length > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM tag value is too long");
+                    throw std::length_error{"OSM tag value is too long"};
                 }
                 add_size(append_with_zero(key,   osmium::memory::item_size_type(key_length)));
                 add_size(append_with_zero(value, osmium::memory::item_size_type(value_length)));
@@ -130,10 +133,10 @@ namespace osmium {
              */
             void add_tag(const std::string& key, const std::string& value) {
                 if (key.size() > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM tag key is too long");
+                    throw std::length_error{"OSM tag key is too long"};
                 }
                 if (value.size() > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM tag value is too long");
+                    throw std::length_error{"OSM tag value is too long"};
                 }
                 add_size(append(key.data(),   osmium::memory::item_size_type(key.size())   + 1));
                 add_size(append(value.data(), osmium::memory::item_size_type(value.size()) + 1));
@@ -185,12 +188,12 @@ namespace osmium {
 
             explicit NodeRefListBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
                 Builder(buffer, parent, sizeof(T)) {
-                new (&item()) T();
+                new (&item()) T{};
             }
 
             explicit NodeRefListBuilder(Builder& parent) :
                 Builder(parent.buffer(), &parent, sizeof(T)) {
-                new (&item()) T();
+                new (&item()) T{};
             }
 
             ~NodeRefListBuilder() {
@@ -198,12 +201,12 @@ namespace osmium {
             }
 
             void add_node_ref(const NodeRef& node_ref) {
-                new (reserve_space_for<osmium::NodeRef>()) osmium::NodeRef(node_ref);
+                new (reserve_space_for<osmium::NodeRef>()) osmium::NodeRef{node_ref};
                 add_size(sizeof(osmium::NodeRef));
             }
 
             void add_node_ref(const object_id_type ref, const osmium::Location& location = Location{}) {
-                add_node_ref(NodeRef(ref, location));
+                add_node_ref(NodeRef{ref, location});
             }
 
         }; // class NodeRefListBuilder
@@ -223,9 +226,9 @@ namespace osmium {
              * @param length Length of role (without \0 termination).
              * @throws std:length_error If role is longer than osmium::max_osm_string_length
              */
-            void add_role(osmium::RelationMember& member, const char* role, const size_t length) {
+            void add_role(osmium::RelationMember& member, const char* role, const std::size_t length) {
                 if (length > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM relation member role is too long");
+                    throw std::length_error{"OSM relation member role is too long"};
                 }
                 member.set_role_size(osmium::string_size_type(length) + 1);
                 add_size(append_with_zero(role, osmium::memory::item_size_type(length)));
@@ -236,12 +239,12 @@ namespace osmium {
 
             explicit RelationMemberListBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
                 Builder(buffer, parent, sizeof(RelationMemberList)) {
-                new (&item()) RelationMemberList();
+                new (&item()) RelationMemberList{};
             }
 
             explicit RelationMemberListBuilder(Builder& parent) :
                 Builder(parent.buffer(), &parent, sizeof(RelationMemberList)) {
-                new (&item()) RelationMemberList();
+                new (&item()) RelationMemberList{};
             }
 
             ~RelationMemberListBuilder() {
@@ -261,9 +264,9 @@ namespace osmium {
              * @throws std:length_error If role_length is greater than
              *         osmium::max_osm_string_length
              */
-            void add_member(osmium::item_type type, object_id_type ref, const char* role, const size_t role_length, const osmium::OSMObject* full_member = nullptr) {
+            void add_member(osmium::item_type type, object_id_type ref, const char* role, const std::size_t role_length, const osmium::OSMObject* full_member = nullptr) {
                 osmium::RelationMember* member = reserve_space_for<osmium::RelationMember>();
-                new (member) osmium::RelationMember(ref, type, full_member != nullptr);
+                new (member) osmium::RelationMember{ref, type, full_member != nullptr};
                 add_size(sizeof(RelationMember));
                 add_role(*member, role, role_length);
                 if (full_member) {
@@ -307,23 +310,19 @@ namespace osmium {
 
             osmium::ChangesetComment* m_comment = nullptr;
 
-            void add_user(osmium::ChangesetComment& comment, const char* user, const size_t length) {
+            void add_user(osmium::ChangesetComment& comment, const char* user, const std::size_t length) {
                 if (length > osmium::max_osm_string_length) {
-                    throw std::length_error("OSM user name is too long");
+                    throw std::length_error{"OSM user name is too long"};
                 }
                 comment.set_user_size(osmium::string_size_type(length) + 1);
                 add_size(append_with_zero(user, osmium::memory::item_size_type(length)));
             }
 
-            void add_text(osmium::ChangesetComment& comment, const char* text, const size_t length) {
-                // XXX There is no limit on the length of a comment text. We
-                // limit it here to 2^16-2 characters, because that's all that
-                // will fit into our internal data structure. This is not ideal,
-                // and will have to be discussed and cleared up.
-                if (length > std::numeric_limits<osmium::string_size_type>::max() - 1) {
-                    throw std::length_error("OSM changeset comment is too long");
+            void add_text(osmium::ChangesetComment& comment, const char* text, const std::size_t length) {
+                if (length > std::numeric_limits<osmium::changeset_comment_size_type>::max() - 1) {
+                    throw std::length_error{"OSM changeset comment is too long"};
                 }
-                comment.set_text_size(osmium::string_size_type(length) + 1);
+                comment.set_text_size(osmium::changeset_comment_size_type(length) + 1);
                 add_size(append_with_zero(text, osmium::memory::item_size_type(length)));
                 add_padding(true);
             }
@@ -332,12 +331,12 @@ namespace osmium {
 
             explicit ChangesetDiscussionBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
                 Builder(buffer, parent, sizeof(ChangesetDiscussion)) {
-                new (&item()) ChangesetDiscussion();
+                new (&item()) ChangesetDiscussion{};
             }
 
             explicit ChangesetDiscussionBuilder(Builder& parent) :
                 Builder(parent.buffer(), &parent, sizeof(ChangesetDiscussion)) {
-                new (&item()) ChangesetDiscussion();
+                new (&item()) ChangesetDiscussion{};
             }
 
             ~ChangesetDiscussionBuilder() {
@@ -348,21 +347,23 @@ namespace osmium {
             void add_comment(osmium::Timestamp date, osmium::user_id_type uid, const char* user) {
                 assert(!m_comment && "You have to always call both add_comment() and then add_comment_text() in that order for each comment!");
                 m_comment = reserve_space_for<osmium::ChangesetComment>();
-                new (m_comment) osmium::ChangesetComment(date, uid);
+                new (m_comment) osmium::ChangesetComment{date, uid};
                 add_size(sizeof(ChangesetComment));
                 add_user(*m_comment, user, std::strlen(user));
             }
 
             void add_comment_text(const char* text) {
                 assert(m_comment && "You have to always call both add_comment() and then add_comment_text() in that order for each comment!");
-                add_text(*m_comment, text, std::strlen(text));
+                osmium::ChangesetComment& comment = *m_comment;
                 m_comment = nullptr;
+                add_text(comment, text, std::strlen(text));
             }
 
             void add_comment_text(const std::string& text) {
                 assert(m_comment && "You have to always call both add_comment() and then add_comment_text() in that order for each comment!");
-                add_text(*m_comment, text.c_str(), text.size());
+                osmium::ChangesetComment& comment = *m_comment;
                 m_comment = nullptr;
+                add_text(comment, text.c_str(), text.size());
             }
 
         }; // class ChangesetDiscussionBuilder
@@ -379,13 +380,13 @@ namespace osmium {
 
             using type = TDerived;
 
-            constexpr static const size_t min_size_for_user = osmium::memory::padded_length(sizeof(string_size_type) + 1);
+            constexpr static const std::size_t min_size_for_user = osmium::memory::padded_length(sizeof(string_size_type) + 1);
 
         public:
 
             explicit OSMObjectBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
                 Builder(buffer, parent, sizeof(T) + min_size_for_user) {
-                new (&item()) T();
+                new (&item()) T{};
                 add_size(min_size_for_user);
                 std::fill_n(object().data() + sizeof(T), min_size_for_user, 0);
                 object().set_user_size(1);
@@ -403,6 +404,17 @@ namespace osmium {
             }
 
             /**
+             * Get a const reference to the object buing built.
+             *
+             * Note that this reference will be invalidated by every action
+             * on the builder that might make the buffer grow. This includes
+             * calls to set_user() and any time a new sub-builder is created.
+             */
+            const T& cobject() const noexcept {
+                return static_cast<const T&>(item());
+            }
+
+            /**
              * Set user name.
              *
              * @param user Pointer to user name.
@@ -410,7 +422,7 @@ namespace osmium {
              */
             TDerived& set_user(const char* user, const string_size_type length) {
                 const auto size_of_object = sizeof(T) + sizeof(string_size_type);
-                assert(object().user_size() == 1 && (size() <= size_of_object + osmium::memory::padded_length(1))
+                assert(cobject().user_size() == 1 && (size() <= size_of_object + osmium::memory::padded_length(1))
                        && "set_user() must be called at most once and before any sub-builders");
                 const auto available_space = min_size_for_user - sizeof(string_size_type) - 1;
                 if (length > available_space) {
@@ -558,13 +570,13 @@ namespace osmium {
 
             using type = ChangesetBuilder;
 
-            constexpr static const size_t min_size_for_user = osmium::memory::padded_length(1);
+            constexpr static const std::size_t min_size_for_user = osmium::memory::padded_length(1);
 
         public:
 
             explicit ChangesetBuilder(osmium::memory::Buffer& buffer, Builder* parent = nullptr) :
                 Builder(buffer, parent, sizeof(Changeset) + min_size_for_user) {
-                new (&item()) Changeset();
+                new (&item()) Changeset{};
                 add_size(min_size_for_user);
                 std::fill_n(object().data() + sizeof(Changeset), min_size_for_user, 0);
                 object().set_user_size(1);
@@ -581,6 +593,17 @@ namespace osmium {
                 return static_cast<Changeset&>(item());
             }
 
+            /**
+             * Get a const reference to the changeset buing built.
+             *
+             * Note that this reference will be invalidated by every action
+             * on the builder that might make the buffer grow. This includes
+             * calls to set_user() and any time a new sub-builder is created.
+             */
+            const Changeset& cobject() const noexcept {
+                return static_cast<const Changeset&>(item());
+            }
+
             OSMIUM_FORWARD(set_id)
             OSMIUM_FORWARD(set_uid)
             OSMIUM_FORWARD(set_uid_from_signed)
@@ -608,7 +631,7 @@ namespace osmium {
              * @param length Length of user name (without \0 termination).
              */
             ChangesetBuilder& set_user(const char* user, const string_size_type length) {
-                assert(object().user_size() == 1 && (size() <= sizeof(Changeset) + osmium::memory::padded_length(1))
+                assert(cobject().user_size() == 1 && (size() <= sizeof(Changeset) + osmium::memory::padded_length(1))
                        && "set_user() must be called at most once and before any sub-builders");
                 const auto available_space = min_size_for_user - 1;
                 if (length > available_space) {
diff --git a/include/osmium/diff_handler.hpp b/include/osmium/diff_handler.hpp
index 19c75b8..d042edd 100644
--- a/include/osmium/diff_handler.hpp
+++ b/include/osmium/diff_handler.hpp
@@ -48,13 +48,13 @@ namespace osmium {
 
             DiffHandler() = default;
 
-            void node(const osmium::DiffNode&) const {
+            void node(const osmium::DiffNode&) const noexcept {
             }
 
-            void way(const osmium::DiffWay&) const {
+            void way(const osmium::DiffWay&) const noexcept {
             }
 
-            void relation(const osmium::DiffRelation&) const {
+            void relation(const osmium::DiffRelation&) const noexcept {
             }
 
         }; // class DiffHandler
diff --git a/include/osmium/diff_iterator.hpp b/include/osmium/diff_iterator.hpp
index 9a70399..c650858 100644
--- a/include/osmium/diff_iterator.hpp
+++ b/include/osmium/diff_iterator.hpp
@@ -66,8 +66,8 @@ namespace osmium {
         void set_diff() const noexcept {
             assert(m_curr != m_end);
 
-            bool use_curr_for_prev =                    m_prev->type() != m_curr->type() || m_prev->id() != m_curr->id();
-            bool use_curr_for_next = m_next == m_end || m_next->type() != m_curr->type() || m_next->id() != m_curr->id();
+            const bool use_curr_for_prev =                    m_prev->type() != m_curr->type() || m_prev->id() != m_curr->id();
+            const bool use_curr_for_next = m_next == m_end || m_next->type() != m_curr->type() || m_next->id() != m_curr->id();
 
             m_diff = std::move(osmium::DiffObject{
                 *(use_curr_for_prev ? m_curr : m_prev),
@@ -104,7 +104,7 @@ namespace osmium {
         }
 
         DiffIterator operator++(int) {
-            DiffIterator tmp(*this);
+            DiffIterator tmp{*this};
             operator++();
             return tmp;
         }
diff --git a/include/osmium/diff_visitor.hpp b/include/osmium/diff_visitor.hpp
index f796ac6..7ede0ee 100644
--- a/include/osmium/diff_visitor.hpp
+++ b/include/osmium/diff_visitor.hpp
@@ -56,7 +56,7 @@ namespace osmium {
                     handler.relation(static_cast<const osmium::DiffRelation&>(diff));
                     break;
                 default:
-                    throw osmium::unknown_type();
+                    throw osmium::unknown_type{};
             }
         }
 
@@ -72,8 +72,8 @@ namespace osmium {
     inline void apply_diff(TIterator it, TIterator end, THandlers&... handlers) {
         using diff_iterator = osmium::DiffIterator<TIterator>;
 
-        diff_iterator dit(it, end);
-        diff_iterator dend(end, end);
+        diff_iterator dit{it, end};
+        diff_iterator dend{end, end};
 
         for (; dit != dend; ++dit) {
             detail::apply_diff_iterator_recurse(*dit, handlers...);
diff --git a/include/osmium/dynamic_handler.hpp b/include/osmium/dynamic_handler.hpp
index 9219ade..8e51852 100644
--- a/include/osmium/dynamic_handler.hpp
+++ b/include/osmium/dynamic_handler.hpp
@@ -54,8 +54,7 @@ namespace osmium {
 
             public:
 
-                virtual ~HandlerWrapperBase() {
-                }
+                virtual ~HandlerWrapperBase() = default;
 
                 virtual void node(const osmium::Node&) {
                 }
@@ -115,7 +114,7 @@ auto _name_##_dispatch(THandler& handler, const osmium::_type_& object, long) ->
             public:
 
                 template <typename... TArgs>
-                HandlerWrapper(TArgs&&... args) :
+                explicit HandlerWrapper(TArgs&&... args) :
                     m_handler(std::forward<TArgs>(args)...) {
                 }
 
@@ -155,12 +154,12 @@ auto _name_##_dispatch(THandler& handler, const osmium::_type_& object, long) ->
         public:
 
             DynamicHandler() :
-                m_impl(impl_ptr(new osmium::handler::detail::HandlerWrapperBase)) {
+                m_impl(new osmium::handler::detail::HandlerWrapperBase) {
             }
 
             template <typename THandler, typename... TArgs>
             void set(TArgs&&... args) {
-                m_impl = impl_ptr(new osmium::handler::detail::HandlerWrapper<THandler>(std::forward<TArgs>(args)...));
+                m_impl.reset(new osmium::handler::detail::HandlerWrapper<THandler>{std::forward<TArgs>(args)...});
             }
 
             void node(const osmium::Node& node) {
diff --git a/include/osmium/experimental/flex_reader.hpp b/include/osmium/experimental/flex_reader.hpp
index 08f426c..2f66ba4 100644
--- a/include/osmium/experimental/flex_reader.hpp
+++ b/include/osmium/experimental/flex_reader.hpp
@@ -78,7 +78,7 @@ namespace osmium {
             {
                 m_location_handler.ignore_errors();
                 if (m_with_areas) {
-                    osmium::io::Reader reader(file, osmium::osm_entity_bits::relation);
+                    osmium::io::Reader reader{file, osmium::osm_entity_bits::relation};
                     m_collector.read_relations(reader);
                     reader.close();
                 }
diff --git a/include/osmium/geom/factory.hpp b/include/osmium/geom/factory.hpp
index 39d1eac..85d0abd 100644
--- a/include/osmium/geom/factory.hpp
+++ b/include/osmium/geom/factory.hpp
@@ -165,6 +165,11 @@ namespace osmium {
 
         public:
 
+            GeometryFactory<TGeomImpl, TProjection>() :
+                m_projection(),
+                m_impl(m_projection.epsg()) {
+            }
+
             /**
              * Constructor for default initialized projection.
              */
@@ -380,9 +385,9 @@ namespace osmium {
                     size_t num_rings = 0;
                     m_impl.multipolygon_start();
 
-                    for (auto it = area.cbegin(); it != area.cend(); ++it) {
-                        if (it->type() == osmium::item_type::outer_ring) {
-                            auto& ring = static_cast<const osmium::OuterRing&>(*it);
+                    for (const auto& item : area) {
+                        if (item.type() == osmium::item_type::outer_ring) {
+                            auto& ring = static_cast<const osmium::OuterRing&>(item);
                             if (num_polygons > 0) {
                                 m_impl.multipolygon_polygon_finish();
                             }
@@ -392,8 +397,8 @@ namespace osmium {
                             m_impl.multipolygon_outer_ring_finish();
                             ++num_rings;
                             ++num_polygons;
-                        } else if (it->type() == osmium::item_type::inner_ring) {
-                            auto& ring = static_cast<const osmium::InnerRing&>(*it);
+                        } else if (item.type() == osmium::item_type::inner_ring) {
+                            auto& ring = static_cast<const osmium::InnerRing&>(item);
                             m_impl.multipolygon_inner_ring_start();
                             add_points(ring);
                             m_impl.multipolygon_inner_ring_finish();
diff --git a/include/osmium/geom/geos.hpp b/include/osmium/geom/geos.hpp
index b472a38..2367b97 100644
--- a/include/osmium/geom/geos.hpp
+++ b/include/osmium/geom/geos.hpp
@@ -129,13 +129,13 @@ namespace osmium {
                  */
                 OSMIUM_DEPRECATED explicit GEOSFactoryImpl(int /* srid */, int srid) :
                     m_precision_model(new geos::geom::PrecisionModel),
-                    m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)),
+                    m_our_geos_factory(new geos::geom::GeometryFactory{m_precision_model.get(), srid}),
                     m_geos_factory(m_our_geos_factory.get()) {
                 }
 
                 explicit GEOSFactoryImpl(int srid) :
                     m_precision_model(new geos::geom::PrecisionModel),
-                    m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)),
+                    m_our_geos_factory(new geos::geom::GeometryFactory{m_precision_model.get(), srid}),
                     m_geos_factory(m_our_geos_factory.get()) {
                 }
 
@@ -143,7 +143,7 @@ namespace osmium {
 
                 point_type make_point(const osmium::geom::Coordinates& xy) const {
                     try {
-                        return point_type(m_geos_factory->createPoint(geos::geom::Coordinate(xy.x, xy.y)));
+                        return point_type{m_geos_factory->createPoint(geos::geom::Coordinate{xy.x, xy.y})};
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -153,7 +153,7 @@ namespace osmium {
 
                 void linestring_start() {
                     try {
-                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
+                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<std::size_t>(0), 2));
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -161,15 +161,15 @@ namespace osmium {
 
                 void linestring_add_location(const osmium::geom::Coordinates& xy) {
                     try {
-                        m_coordinate_sequence->add(geos::geom::Coordinate(xy.x, xy.y));
+                        m_coordinate_sequence->add(geos::geom::Coordinate{xy.x, xy.y});
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
                 }
 
-                linestring_type linestring_finish(size_t /* num_points */) {
+                linestring_type linestring_finish(std::size_t /* num_points */) {
                     try {
-                        return linestring_type(m_geos_factory->createLineString(m_coordinate_sequence.release()));
+                        return linestring_type{m_geos_factory->createLineString(m_coordinate_sequence.release())};
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -201,7 +201,7 @@ namespace osmium {
 
                 void multipolygon_outer_ring_start() {
                     try {
-                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
+                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<std::size_t>(0), 2));
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -217,7 +217,7 @@ namespace osmium {
 
                 void multipolygon_inner_ring_start() {
                     try {
-                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
+                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<std::size_t>(0), 2));
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -233,7 +233,7 @@ namespace osmium {
 
                 void multipolygon_add_location(const osmium::geom::Coordinates& xy) {
                     try {
-                        m_coordinate_sequence->add(geos::geom::Coordinate(xy.x, xy.y));
+                        m_coordinate_sequence->add(geos::geom::Coordinate{xy.x, xy.y});
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -246,7 +246,7 @@ namespace osmium {
                             return p.release();
                         });
                         m_polygons.clear();
-                        return multipolygon_type(m_geos_factory->createMultiPolygon(polygons));
+                        return multipolygon_type{m_geos_factory->createMultiPolygon(polygons)};
                     } catch (const geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
diff --git a/include/osmium/geom/mercator_projection.hpp b/include/osmium/geom/mercator_projection.hpp
index a6b798a..5be84a4 100644
--- a/include/osmium/geom/mercator_projection.hpp
+++ b/include/osmium/geom/mercator_projection.hpp
@@ -138,6 +138,9 @@ namespace osmium {
 
         public:
 
+            MercatorProjection() {
+            }
+
             Coordinates operator()(osmium::Location location) const {
                 return Coordinates{detail::lon_to_x(location.lon()), detail::lat_to_y(location.lat())};
             }
diff --git a/include/osmium/geom/projection.hpp b/include/osmium/geom/projection.hpp
index 132b068..de3bf90 100644
--- a/include/osmium/geom/projection.hpp
+++ b/include/osmium/geom/projection.hpp
@@ -111,10 +111,11 @@ namespace osmium {
          *
          * @throws osmium::projection_error if the projection fails
          */
+        // cppcheck-suppress passedByValue (because c is small and we want to change it)
         inline Coordinates transform(const CRS& src, const CRS& dest, Coordinates c) {
             const int result = pj_transform(src.get(), dest.get(), 1, 1, &c.x, &c.y, nullptr);
             if (result != 0) {
-                throw osmium::projection_error{std::string("projection failed: ") + pj_strerrno(result)};
+                throw osmium::projection_error{std::string{"projection failed: "} + pj_strerrno(result)};
             }
             return c;
         }
diff --git a/include/osmium/geom/relations.hpp b/include/osmium/geom/relations.hpp
index f11ba51..c8d5caf 100644
--- a/include/osmium/geom/relations.hpp
+++ b/include/osmium/geom/relations.hpp
@@ -43,13 +43,23 @@ namespace osmium {
         /**
          * Check whether one geometry contains another.
          */
-        inline bool contains(const osmium::Box& lhs, const osmium::Box& rhs) {
+        inline bool contains(const osmium::Box& lhs, const osmium::Box& rhs) noexcept {
             return ((lhs.bottom_left().x() >= rhs.bottom_left().x()) &&
                     (lhs.top_right().x()   <= rhs.top_right().x())   &&
                     (lhs.bottom_left().y() >= rhs.bottom_left().y()) &&
                     (lhs.top_right().y()   <= rhs.top_right().y()));
         }
 
+        /**
+         * Check whether one geometry overlaps another.
+         */
+        inline bool overlaps(const osmium::Box& lhs, const osmium::Box& rhs) noexcept {
+            return ((lhs.bottom_left().x() <= rhs.top_right().x()) &&
+                    (lhs.bottom_left().y() <= rhs.top_right().y()) &&
+                    (rhs.bottom_left().x() <= lhs.top_right().x()) &&
+                    (rhs.bottom_left().y() <= lhs.top_right().y()));
+        }
+
     } // namespace geom
 
 } // namespace osmium
diff --git a/include/osmium/geom/tile.hpp b/include/osmium/geom/tile.hpp
index 55ea87b..6f07d8c 100644
--- a/include/osmium/geom/tile.hpp
+++ b/include/osmium/geom/tile.hpp
@@ -62,7 +62,7 @@ namespace osmium {
         }
 
         /**
-         * Returns the width or hight of a tile in web mercator coordinates for
+         * Returns the width or height of a tile in web mercator coordinates for
          * the given zoom level.
          */
         inline constexpr double tile_extent_in_zoom(uint32_t zoom) noexcept {
@@ -108,7 +108,7 @@ namespace osmium {
             uint32_t z;
 
             /**
-             * Create a tile with the given zoom level and x any y tile
+             * Create a tile with the given zoom level and x and y tile
              * coordinates.
              *
              * The values are not checked for validity.
@@ -172,22 +172,30 @@ namespace osmium {
         }; // struct Tile
 
         /// Tiles are equal if all their attributes are equal.
-        inline bool operator==(const Tile& lhs, const Tile& rhs) {
+        inline bool operator==(const Tile& lhs, const Tile& rhs) noexcept {
             return lhs.z == rhs.z && lhs.x == rhs.x && lhs.y == rhs.y;
         }
 
-        inline bool operator!=(const Tile& lhs, const Tile& rhs) {
+        inline bool operator!=(const Tile& lhs, const Tile& rhs) noexcept {
             return ! (lhs == rhs);
         }
 
         /**
          * This defines an arbitrary order on tiles for use in std::map etc.
          */
-        inline bool operator<(const Tile& lhs, const Tile& rhs) {
-            if (lhs.z < rhs.z) return true;
-            if (lhs.z > rhs.z) return false;
-            if (lhs.x < rhs.x) return true;
-            if (lhs.x > rhs.x) return false;
+        inline bool operator<(const Tile& lhs, const Tile& rhs) noexcept {
+            if (lhs.z < rhs.z) {
+                return true;
+            }
+            if (lhs.z > rhs.z) {
+                return false;
+            }
+            if (lhs.x < rhs.x) {
+                return true;
+            }
+            if (lhs.x > rhs.x) {
+                return false;
+            }
             return lhs.y < rhs.y;
         }
 
diff --git a/include/osmium/geom/wkb.hpp b/include/osmium/geom/wkb.hpp
index 39105fc..5299d45 100644
--- a/include/osmium/geom/wkb.hpp
+++ b/include/osmium/geom/wkb.hpp
@@ -108,19 +108,19 @@ namespace osmium {
                 }; // enum class wkb_byte_order_type
 
                 std::string m_data;
-                uint32_t m_points {0};
+                uint32_t m_points = 0;
                 int m_srid;
                 wkb_type m_wkb_type;
                 out_type m_out_type;
 
-                size_t m_linestring_size_offset = 0;
-                size_t m_polygons = 0;
-                size_t m_rings = 0;
-                size_t m_multipolygon_size_offset = 0;
-                size_t m_polygon_size_offset = 0;
-                size_t m_ring_size_offset = 0;
+                std::size_t m_linestring_size_offset = 0;
+                std::size_t m_polygons = 0;
+                std::size_t m_rings = 0;
+                std::size_t m_multipolygon_size_offset = 0;
+                std::size_t m_polygon_size_offset = 0;
+                std::size_t m_ring_size_offset = 0;
 
-                size_t header(std::string& str, wkbGeometryType type, bool add_length) const {
+                std::size_t header(std::string& str, wkbGeometryType type, bool add_length) const {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
                     str_push(str, wkb_byte_order_type::NDR);
 #else
@@ -132,14 +132,14 @@ namespace osmium {
                     } else {
                         str_push(str, type);
                     }
-                    const size_t offset = str.size();
+                    const std::size_t offset = str.size();
                     if (add_length) {
                         str_push(str, static_cast<uint32_t>(0));
                     }
                     return offset;
                 }
 
-                void set_size(const size_t offset, const size_t size) {
+                void set_size(const std::size_t offset, const std::size_t size) {
                     uint32_t s = static_cast_with_assert<uint32_t>(size);
                     std::copy_n(reinterpret_cast<char*>(&s), sizeof(uint32_t), &m_data[offset]);
                 }
@@ -185,7 +185,7 @@ namespace osmium {
                     str_push(m_data, xy.y);
                 }
 
-                linestring_type linestring_finish(size_t num_points) {
+                linestring_type linestring_finish(std::size_t num_points) {
                     set_size(m_linestring_size_offset, num_points);
                     std::string data;
 
diff --git a/include/osmium/handler/check_order.hpp b/include/osmium/handler/check_order.hpp
index 46411da..48a673b 100644
--- a/include/osmium/handler/check_order.hpp
+++ b/include/osmium/handler/check_order.hpp
@@ -39,6 +39,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <osmium/handler.hpp>
 #include <osmium/osm/node.hpp>
+#include <osmium/osm/object_comparisons.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/types.hpp>
 #include <osmium/osm/way.hpp>
@@ -71,7 +72,9 @@ namespace osmium {
          * Handler that can be used to check that an OSM file is ordered
          * correctly. Ordered in this case refers to the usual order in OSM
          * files: First nodes in the order of their IDs, then ways in the order
-         * of their IDs, then relations in the order or their IDs.
+         * of their IDs, then relations in the order or their IDs. Negative
+         * IDs are ordered first then positive IDs, both ordered by absolute
+         * value.
          *
          * IDs have to be unique for each type. This check will fail for
          * history files.
@@ -90,31 +93,31 @@ namespace osmium {
         public:
 
             void node(const osmium::Node& node) {
-                if (m_max_way_id > 0) {
+                if (m_max_way_id > std::numeric_limits<osmium::object_id_type>::min()) {
                     throw out_of_order_error{"Found a node after a way.", node.id()};
                 }
-                if (m_max_relation_id > 0) {
+                if (m_max_relation_id > std::numeric_limits<osmium::object_id_type>::min()) {
                     throw out_of_order_error{"Found a node after a relation.", node.id()};
                 }
 
                 if (m_max_node_id == node.id()) {
-                    throw out_of_order_error{"Node ID twice in input. Maybe you are using a history or changes file?", node.id()};
+                    throw out_of_order_error{"Node ID twice in input. Maybe you are using a history or change file?", node.id()};
                 }
-                if (m_max_node_id > node.id()) {
+                if (id_order{}(node.id(), m_max_node_id)) {
                     throw out_of_order_error{"Node IDs out of order.", node.id()};
                 }
                 m_max_node_id = node.id();
             }
 
             void way(const osmium::Way& way) {
-                if (m_max_relation_id > 0) {
+                if (m_max_relation_id > std::numeric_limits<osmium::object_id_type>::min()) {
                     throw out_of_order_error{"Found a way after a relation.", way.id()};
                 }
 
                 if (m_max_way_id == way.id()) {
-                    throw out_of_order_error{"Way ID twice in input. Maybe you are using a history or changes file?", way.id()};
+                    throw out_of_order_error{"Way ID twice in input. Maybe you are using a history or change file?", way.id()};
                 }
-                if (m_max_way_id > way.id()) {
+                if (id_order{}(way.id(), m_max_way_id)) {
                     throw out_of_order_error{"Way IDs out of order.", way.id()};
                 }
                 m_max_way_id = way.id();
@@ -122,9 +125,9 @@ namespace osmium {
 
             void relation(const osmium::Relation& relation) {
                 if (m_max_relation_id == relation.id()) {
-                    throw out_of_order_error{"Relation ID twice in input. Maybe you are using a history or changes file?", relation.id()};
+                    throw out_of_order_error{"Relation ID twice in input. Maybe you are using a history or change file?", relation.id()};
                 }
-                if (m_max_relation_id > relation.id()) {
+                if (id_order{}(relation.id(), m_max_relation_id)) {
                     throw out_of_order_error{"Relation IDs out of order.", relation.id()};
                 }
                 m_max_relation_id = relation.id();
diff --git a/include/osmium/handler/disk_store.hpp b/include/osmium/handler/disk_store.hpp
index 882d7a1..da1119d 100644
--- a/include/osmium/handler/disk_store.hpp
+++ b/include/osmium/handler/disk_store.hpp
@@ -60,9 +60,9 @@ namespace osmium {
          */
         class DiskStore : public osmium::handler::Handler {
 
-            using offset_index_type = osmium::index::map::Map<unsigned_object_id_type, size_t>;
+            using offset_index_type = osmium::index::map::Map<unsigned_object_id_type, std::size_t>;
 
-            size_t m_offset = 0;
+            std::size_t m_offset = 0;
             int m_data_fd;
 
             offset_index_type& m_node_index;
diff --git a/include/osmium/handler/dump.hpp b/include/osmium/handler/dump.hpp
index b3f2ed7..9c2c5c6 100644
--- a/include/osmium/handler/dump.hpp
+++ b/include/osmium/handler/dump.hpp
@@ -108,7 +108,7 @@ namespace osmium {
                        << (object.visible() ? "yes" : "no")
                        << "\n";
 
-                Dump dump(*m_out, m_with_size, m_prefix + "  ");
+                Dump dump{*m_out, m_with_size, m_prefix + "  "};
                 osmium::apply(object.cbegin(), object.cend(), dump);
             }
 
@@ -281,7 +281,7 @@ namespace osmium {
 
                 *m_out << "\n";
 
-                Dump dump(*m_out, m_with_size, m_prefix + "  ");
+                Dump dump{*m_out, m_with_size, m_prefix + "  "};
                 osmium::apply(changeset.cbegin(), changeset.cend(), dump);
             }
 
diff --git a/include/osmium/handler/node_locations_for_ways.hpp b/include/osmium/handler/node_locations_for_ways.hpp
index 84f77d1..ff3fbe9 100644
--- a/include/osmium/handler/node_locations_for_ways.hpp
+++ b/include/osmium/handler/node_locations_for_ways.hpp
@@ -64,9 +64,11 @@ namespace osmium {
         template <typename TStoragePosIDs, typename TStorageNegIDs = dummy_type>
         class NodeLocationsForWays : public osmium::handler::Handler {
 
-            static_assert(std::is_base_of<osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>, TStoragePosIDs>::value, "Index class must be derived from osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>");
+            template <typename T>
+            using based_on_map = std::is_base_of<osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>, T>;
 
-            static_assert(std::is_base_of<osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>, TStorageNegIDs>::value, "Index class must be derived from osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>");
+            static_assert(based_on_map<TStoragePosIDs>::value, "Index class must be derived from osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>");
+            static_assert(based_on_map<TStorageNegIDs>::value, "Index class must be derived from osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>");
 
         public:
 
@@ -81,11 +83,11 @@ namespace osmium {
             /// Object that handles the actual storage of the node locations (with negative IDs).
             TStorageNegIDs& m_storage_neg;
 
-            osmium::unsigned_object_id_type m_last_id{0};
+            osmium::unsigned_object_id_type m_last_id = 0;
 
-            bool m_ignore_errors{false};
+            bool m_ignore_errors = false;
 
-            bool m_must_sort{false};
+            bool m_must_sort = false;
 
             // It is okay to have this static dummy instance, even when using several threads,
             // because it is read-only.
@@ -123,7 +125,7 @@ namespace osmium {
                 }
                 m_last_id = node.positive_id();
 
-                const osmium::object_id_type id = node.id();
+                const auto id = node.id();
                 if (id >= 0) {
                     m_storage_pos.set(static_cast<osmium::unsigned_object_id_type>( id), node.location());
                 } else {
diff --git a/include/osmium/index/detail/create_map_with_fd.hpp b/include/osmium/index/detail/create_map_with_fd.hpp
index e724f18..745a358 100644
--- a/include/osmium/index/detail/create_map_with_fd.hpp
+++ b/include/osmium/index/detail/create_map_with_fd.hpp
@@ -50,15 +50,15 @@ namespace osmium {
             template <typename T>
             inline T* create_map_with_fd(const std::vector<std::string>& config) {
                 if (config.size() == 1) {
-                    return new T();
+                    return new T{};
                 }
                 assert(config.size() > 1);
                 const std::string& filename = config[1];
                 const int fd = ::open(filename.c_str(), O_CREAT | O_RDWR, 0644);
                 if (fd == -1) {
-                    throw std::runtime_error(std::string("can't open file '") + filename + "': " + std::strerror(errno));
+                    throw std::runtime_error{std::string{"can't open file '"} + filename + "': " + std::strerror(errno)};
                 }
-                return new T(fd);
+                return new T{fd};
             }
 
         } // namespace detail
diff --git a/include/osmium/index/detail/mmap_vector_file.hpp b/include/osmium/index/detail/mmap_vector_file.hpp
index 6ba7e2e..ff0ca54 100644
--- a/include/osmium/index/detail/mmap_vector_file.hpp
+++ b/include/osmium/index/detail/mmap_vector_file.hpp
@@ -53,11 +53,11 @@ namespace osmium {
         template <typename T>
         class mmap_vector_file : public mmap_vector_base<T> {
 
-            size_t filesize(int fd) const {
-                const size_t size = osmium::util::file_size(fd);
+            std::size_t filesize(int fd) const {
+                const auto size = osmium::util::file_size(fd);
 
                 if (size % sizeof(T) != 0) {
-                    throw std::runtime_error("Index file has wrong size (must be multiple of " + std::to_string(sizeof(T)) + ").");
+                    throw std::runtime_error{"Index file has wrong size (must be multiple of " + std::to_string(sizeof(T)) + ")."};
                 }
 
                 return size / sizeof(T);
diff --git a/include/osmium/index/detail/tmpfile.hpp b/include/osmium/index/detail/tmpfile.hpp
index 9da7b70..ce9b194 100644
--- a/include/osmium/index/detail/tmpfile.hpp
+++ b/include/osmium/index/detail/tmpfile.hpp
@@ -50,7 +50,7 @@ namespace osmium {
         inline int create_tmp_file() {
             FILE* file = ::tmpfile();
             if (!file) {
-                throw std::system_error(errno, std::system_category(), "tempfile failed");
+                throw std::system_error{errno, std::system_category(), "tempfile failed"};
             }
             return fileno(file);
         }
diff --git a/include/osmium/index/detail/vector_map.hpp b/include/osmium/index/detail/vector_map.hpp
index ed0f760..0eb49d5 100644
--- a/include/osmium/index/detail/vector_map.hpp
+++ b/include/osmium/index/detail/vector_map.hpp
@@ -35,7 +35,6 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cstddef>
-#include <stdexcept>
 #include <utility>
 
 #include <osmium/index/index.hpp>
@@ -70,7 +69,7 @@ namespace osmium {
 
                 ~VectorBasedDenseMap() noexcept final = default;
 
-                void reserve(const size_t size) final {
+                void reserve(const std::size_t size) final {
                     m_vector.reserve(size);
                 }
 
@@ -99,15 +98,15 @@ namespace osmium {
                     return m_vector[id];
                 }
 
-                size_t size() const final {
+                std::size_t size() const final {
                     return m_vector.size();
                 }
 
-                size_t byte_size() const {
+                std::size_t byte_size() const {
                     return m_vector.size() * sizeof(element_type);
                 }
 
-                size_t used_memory() const final {
+                std::size_t used_memory() const final {
                     return sizeof(TValue) * size();
                 }
 
@@ -205,15 +204,15 @@ namespace osmium {
                     return result->second;
                 }
 
-                size_t size() const final {
+                std::size_t size() const final {
                     return m_vector.size();
                 }
 
-                size_t byte_size() const {
+                std::size_t byte_size() const {
                     return m_vector.size() * sizeof(element_type);
                 }
 
-                size_t used_memory() const final {
+                std::size_t used_memory() const final {
                     return sizeof(element_type) * size();
                 }
 
diff --git a/include/osmium/index/id_set.hpp b/include/osmium/index/id_set.hpp
index 9f36314..d86973d 100644
--- a/include/osmium/index/id_set.hpp
+++ b/include/osmium/index/id_set.hpp
@@ -35,10 +35,11 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
+#include <cstddef>
 #include <cstring>
+#include <iterator>
 #include <memory>
 #include <type_traits>
-#include <unordered_set>
 #include <vector>
 
 #include <osmium/osm/item_type.hpp>
@@ -57,8 +58,7 @@ namespace osmium {
 
         public:
 
-            virtual ~IdSet() {
-            }
+            virtual ~IdSet() = default;
 
             /**
              * Add the given Id to the set.
@@ -80,6 +80,11 @@ namespace osmium {
              */
             virtual void clear() = 0;
 
+            /**
+             * Get an estimate of the amount of memory used for the set.
+             */
+            virtual std::size_t used_memory() const noexcept = 0;
+
         }; // class IdSet
 
         template <typename T>
@@ -139,7 +144,7 @@ namespace osmium {
             }
 
             IdSetDenseIterator<T> operator++(int) noexcept {
-                IdSetDenseIterator<T> tmp(*this);
+                IdSetDenseIterator<T> tmp{*this};
                 operator++();
                 return tmp;
             }
@@ -178,17 +183,17 @@ namespace osmium {
             // which would mean less (but larger) memory allocations. For
             // relations Ids it could be smaller, because they would all fit
             // into a smaller allocation.
-            constexpr static const size_t chunk_bits = 22;
-            constexpr static const size_t chunk_size = 1 << chunk_bits;
+            constexpr static const std::size_t chunk_bits = 22;
+            constexpr static const std::size_t chunk_size = 1 << chunk_bits;
 
             std::vector<std::unique_ptr<unsigned char[]>> m_data;
             T m_size = 0;
 
-            static size_t chunk_id(T id) noexcept {
+            static std::size_t chunk_id(T id) noexcept {
                 return id >> (chunk_bits + 3);
             }
 
-            static size_t offset(T id) noexcept {
+            static std::size_t offset(T id) noexcept {
                 return (id >> 3) & ((1 << chunk_bits) - 1);
             }
 
@@ -244,7 +249,7 @@ namespace osmium {
              *
              * @param id The Id to set.
              */
-            void set(T id) override final {
+            void set(T id) final {
                 (void)check_and_set(id);
             }
 
@@ -267,7 +272,7 @@ namespace osmium {
              *
              * @param id The Id to check.
              */
-            bool get(T id) const noexcept override final {
+            bool get(T id) const noexcept final {
                 if (chunk_id(id) >= m_data.size()) {
                     return false;
                 }
@@ -281,7 +286,7 @@ namespace osmium {
             /**
              * Is the set empty?
              */
-            bool empty() const noexcept override final {
+            bool empty() const noexcept final {
                 return m_size == 0;
             }
 
@@ -295,17 +300,21 @@ namespace osmium {
             /**
              * Clear the set.
              */
-            void clear() override final {
+            void clear() final {
                 m_data.clear();
                 m_size = 0;
             }
 
+            std::size_t used_memory() const noexcept final {
+                return m_data.size() * chunk_size;
+            }
+
             IdSetDenseIterator<T> begin() const {
-                return IdSetDenseIterator<T>{this, 0, last()};
+                return {this, 0, last()};
             }
 
             IdSetDenseIterator<T> end() const {
-                return IdSetDenseIterator<T>{this, last(), last()};
+                return {this, last(), last()};
             }
 
         }; // class IdSetDense
@@ -324,7 +333,7 @@ namespace osmium {
             /**
              * Add the given Id to the set.
              */
-            void set(T id) override final {
+            void set(T id) final {
                 m_data.push_back(id);
             }
 
@@ -333,7 +342,7 @@ namespace osmium {
              *
              * @param id The Id to check.
              */
-            bool get(T id) const noexcept override final {
+            bool get(T id) const noexcept final {
                 const auto it = std::find(m_data.cbegin(), m_data.cend(), id);
                 return it != m_data.cend();
             }
@@ -355,14 +364,14 @@ namespace osmium {
             /**
              * Is the set empty?
              */
-            bool empty() const noexcept override final {
+            bool empty() const noexcept final {
                 return m_data.empty();
             }
 
             /**
              * Clear the set.
              */
-            void clear() override final {
+            void clear() final {
                 m_data.clear();
             }
 
@@ -383,10 +392,14 @@ namespace osmium {
              * @pre You must have called sort_unique() before calling this
              *      or be sure there are no duplicates.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 return m_data.size();
             }
 
+            std::size_t used_memory() const noexcept final {
+                return m_data.capacity() * sizeof(T);
+            }
+
             /// Iterator type. There is no non-const iterator.
             using const_iterator = typename std::vector<T>::const_iterator;
 
@@ -408,7 +421,7 @@ namespace osmium {
 
         }; // class IdSetSmall
 
-        /// @deprecated Use nrw_array helper class instead.
+        /// @deprecated Use nwr_array helper class instead.
         template <template<typename> class IdSetType>
         class NWRIdSet {
 
diff --git a/include/osmium/index/index.hpp b/include/osmium/index/index.hpp
index 565009c..325b42f 100644
--- a/include/osmium/index/index.hpp
+++ b/include/osmium/index/index.hpp
@@ -34,12 +34,11 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cstddef>
+#include <cstdint>
 #include <limits>
 #include <stdexcept>
 #include <string>
 
-#include <osmium/util/compatibility.hpp>
-
 namespace osmium {
 
     /**
diff --git a/include/osmium/index/map.hpp b/include/osmium/index/map.hpp
index bcda589..e0f9aec 100644
--- a/include/osmium/index/map.hpp
+++ b/include/osmium/index/map.hpp
@@ -43,7 +43,6 @@ DEALINGS IN THE SOFTWARE.
 #include <type_traits>
 #include <vector>
 
-#include <osmium/util/compatibility.hpp>
 #include <osmium/util/string.hpp>
 
 namespace osmium {
@@ -181,14 +180,14 @@ namespace osmium {
                 // but not always. It could, for instance, sort internal data.
                 // This is why it is not declared const here.
                 virtual void dump_as_list(const int /*fd*/) {
-                    throw std::runtime_error("can't dump as list");
+                    throw std::runtime_error{"can't dump as list"};
                 }
 
                 // This function can usually be const in derived classes,
                 // but not always. It could, for instance, sort internal data.
                 // This is why it is not declared const here.
                 virtual void dump_as_array(const int /*fd*/) {
-                    throw std::runtime_error("can't dump as array");
+                    throw std::runtime_error{"can't dump as array"};
                 }
 
             }; // class Map
@@ -245,13 +244,13 @@ namespace osmium {
             }
 
             std::unique_ptr<map_type> create_map(const std::string& config_string) const {
-                std::vector<std::string> config = osmium::split_string(config_string, ',');
+                std::vector<std::string> config{osmium::split_string(config_string, ',')};
 
                 if (config.empty()) {
                     throw map_factory_error{"Need non-empty map type name"};
                 }
 
-                auto it = m_callbacks.find(config[0]);
+                const auto it = m_callbacks.find(config[0]);
                 if (it != m_callbacks.end()) {
                     return std::unique_ptr<map_type>((it->second)(config));
                 }
diff --git a/include/osmium/index/map/all.hpp b/include/osmium/index/map/all.hpp
index 1e4827f..f196ffe 100644
--- a/include/osmium/index/map/all.hpp
+++ b/include/osmium/index/map/all.hpp
@@ -37,6 +37,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/index/map/dense_mem_array.hpp>   // IWYU pragma: keep
 #include <osmium/index/map/dense_mmap_array.hpp>  // IWYU pragma: keep
 #include <osmium/index/map/dummy.hpp>             // IWYU pragma: keep
+#include <osmium/index/map/flex_mem.hpp>          // IWYU pragma: keep
 #include <osmium/index/map/sparse_file_array.hpp> // IWYU pragma: keep
 #include <osmium/index/map/sparse_mem_array.hpp>  // IWYU pragma: keep
 #include <osmium/index/map/sparse_mem_map.hpp>    // IWYU pragma: keep
diff --git a/include/osmium/index/map/flex_mem.hpp b/include/osmium/index/map/flex_mem.hpp
new file mode 100644
index 0000000..db474e7
--- /dev/null
+++ b/include/osmium/index/map/flex_mem.hpp
@@ -0,0 +1,285 @@
+#ifndef OSMIUM_INDEX_MAP_FLEX_MEM_HPP
+#define OSMIUM_INDEX_MAP_FLEX_MEM_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include <osmium/index/map.hpp>
+#include <osmium/index/index.hpp>
+
+#define OSMIUM_HAS_INDEX_MAP_FLEX_MEM
+
+namespace osmium {
+
+    namespace index {
+
+        namespace map {
+
+            /**
+             * This is an autoscaling index that works well with small and
+             * large input data. All data will be held in memory. For small
+             * input data a sparse array will be used, if this becomes
+             * inefficient, the class will switch automatically to a dense
+             * index.
+             */
+            template <typename TId, typename TValue>
+            class FlexMem : public osmium::index::map::Map<TId, TValue> {
+
+                // This value is based on benchmarks with a planet file and
+                // some smaller files.
+                enum constant_bits {
+                    bits = 16
+                };
+
+                enum constant_block_size : uint64_t {
+                    block_size = 1ll << bits
+                };
+
+                // Minimum number of entries in the sparse index before we
+                // are considering switching to a dense index.
+                enum constant_min_dense_entries : int64_t {
+                    min_dense_entries = 0xffffff
+                };
+
+                // When more than a third of all Ids are in the index, we
+                // switch to the dense index. This is a compromise between
+                // the best memory efficiency (which we would get at a factor
+                // of 2) and the performance (dense index is much faster then
+                // the sparse index).
+                enum constant_density_factor {
+                    density_factor = 3
+                };
+
+                // An entry in the sparse index
+                struct entry {
+                    uint64_t id;
+                    TValue value;
+
+                    entry(uint64_t i, TValue v) :
+                        id(i),
+                        value(v) {
+                    }
+
+                    bool operator<(const entry other) const noexcept {
+                        return id < other.id;
+                    }
+                };
+
+                std::vector<entry> m_sparse_entries;
+
+                std::vector<std::vector<TValue>> m_dense_blocks;
+
+                // The maximum Id that was seen yet. Only set in sparse mode.
+                uint64_t m_max_id = 0;
+
+                // Set to false in sparse mode and to true in dense mode.
+                bool m_dense;
+
+                static uint64_t block(const uint64_t id) noexcept {
+                    return id >> bits;
+                }
+
+                static uint64_t offset(const uint64_t id) noexcept {
+                    return id & (block_size - 1);
+                }
+
+                // Assure that the block with the given number exists. Create
+                // it if needed.
+                void assure_block(const uint64_t num) {
+                    if (num >= m_dense_blocks.size()) {
+                        m_dense_blocks.resize(num + 1);
+                    }
+                    if (m_dense_blocks[num].empty()) {
+                        m_dense_blocks[num].assign(block_size, osmium::index::empty_value<TValue>());
+                    }
+                }
+
+                void set_sparse(const uint64_t id, const TValue value) {
+                    m_sparse_entries.emplace_back(id, value);
+                    if (id > m_max_id) {
+                        m_max_id = id;
+
+                        if (m_sparse_entries.size() >= min_dense_entries) {
+                            if (m_max_id < m_sparse_entries.size() * density_factor) {
+                                switch_to_dense();
+                            }
+                        }
+                    }
+                }
+
+                TValue get_sparse(const uint64_t id) const noexcept {
+                    const auto it = std::lower_bound(m_sparse_entries.begin(),
+                                                     m_sparse_entries.end(),
+                                                     entry{id, osmium::index::empty_value<TValue>()});
+                    if (it == m_sparse_entries.end() || it->id != id) {
+                        return osmium::index::empty_value<TValue>();
+                    }
+                    return it->value;
+                }
+
+                void set_dense(const uint64_t id, const TValue value) {
+                    assure_block(block(id));
+                    m_dense_blocks[block(id)][offset(id)] = value;
+                }
+
+                TValue get_dense(const uint64_t id) const noexcept {
+                    if (m_dense_blocks.size() <= block(id) || m_dense_blocks[block(id)].empty()) {
+                        return osmium::index::empty_value<TValue>();
+                    }
+                    return m_dense_blocks[block(id)][offset(id)];
+                }
+
+            public:
+
+                /**
+                 * Create FlexMem index.
+                 *
+                 * @param use_dense Usually FlexMem indexes start out as sparse
+                 *                  indexes and will switch to dense when they
+                 *                  think it is better. Set this to force dense
+                 *                  indexing from the start. This is usually
+                 *                  only useful for testing.
+                 */
+                explicit FlexMem(bool use_dense = false) :
+                    m_dense(use_dense) {
+                }
+
+                ~FlexMem() noexcept final = default;
+
+                bool is_dense() const noexcept {
+                    return m_dense;
+                }
+
+                std::size_t size() const noexcept final {
+                    if (m_dense) {
+                        return m_dense_blocks.size() * block_size;
+                    }
+                    return m_sparse_entries.size();
+                }
+
+                std::size_t used_memory() const noexcept final {
+                    return sizeof(FlexMem) +
+                           m_sparse_entries.size() * sizeof(entry) +
+                           m_dense_blocks.size() * (block_size * sizeof(TValue) + sizeof(std::vector<TValue>));
+                }
+
+                void set(const TId id, const TValue value) final {
+                    if (m_dense) {
+                        set_dense(id, value);
+                    } else {
+                        set_sparse(id, value);
+                    }
+                }
+
+                TValue get_noexcept(const TId id) const noexcept final {
+                    if (m_dense) {
+                        return get_dense(id);
+                    }
+                    return get_sparse(id);
+                }
+
+                TValue get(const TId id) const final {
+                    const auto value = get_noexcept(id);
+                    if (value == osmium::index::empty_value<TValue>()) {
+                        throw osmium::not_found{id};
+                    }
+                    return value;
+                }
+
+                void clear() final {
+                    m_sparse_entries.clear();
+                    m_sparse_entries.shrink_to_fit();
+                    m_dense_blocks.clear();
+                    m_dense_blocks.shrink_to_fit();
+                    m_max_id = 0;
+                    m_dense = false;
+                }
+
+                void sort() final {
+                    std::sort(m_sparse_entries.begin(), m_sparse_entries.end());
+                }
+
+                /**
+                 * Switch from using a sparse to a dense index. Usually you
+                 * do not need to call this, because the FlexMem class will
+                 * do this automatically if it thinks the dense index is more
+                 * efficient.
+                 *
+                 * Does nothing if the index is already in dense mode.
+                 */
+                void switch_to_dense() {
+                    if (m_dense) {
+                        return;
+                    }
+                    for (const auto entry : m_sparse_entries) {
+                        set_dense(entry.id, entry.value);
+                    }
+                    m_sparse_entries.clear();
+                    m_sparse_entries.shrink_to_fit();
+                    m_max_id = 0;
+                    m_dense = true;
+                }
+
+                std::pair<std::size_t, std::size_t> stats() const noexcept {
+                    std::size_t used_blocks = 0;
+                    std::size_t empty_blocks = 0;
+
+                    for (const auto& block : m_dense_blocks) {
+                        if (block.empty()) {
+                            ++empty_blocks;
+                        } else {
+                            ++used_blocks;
+                        }
+                    }
+
+                    return std::make_pair(used_blocks, empty_blocks);
+                }
+
+            }; // class FlexMem
+
+        } // namespace map
+
+    } // namespace index
+
+} // namespace osmium
+
+#ifdef OSMIUM_WANT_NODE_LOCATION_MAPS
+    REGISTER_MAP(osmium::unsigned_object_id_type, osmium::Location, osmium::index::map::FlexMem, flex_mem)
+#endif
+
+#endif // OSMIUM_INDEX_MAP_FLEX_MEM_HPP
diff --git a/include/osmium/index/node_locations_map.hpp b/include/osmium/index/node_locations_map.hpp
index 4b096c4..0e78de1 100644
--- a/include/osmium/index/node_locations_map.hpp
+++ b/include/osmium/index/node_locations_map.hpp
@@ -69,4 +69,8 @@ DEALINGS IN THE SOFTWARE.
     REGISTER_MAP(osmium::unsigned_object_id_type, osmium::Location, osmium::index::map::SparseMmapArray, sparse_mmap_array)
 #endif
 
+#ifdef OSMIUM_HAS_INDEX_MAP_FLEX_MEM
+    REGISTER_MAP(osmium::unsigned_object_id_type, osmium::Location, osmium::index::map::FlexMem, flex_mem)
+#endif
+
 #endif // OSMIUM_INDEX_NODE_LOCATIONS_MAP_HPP
diff --git a/include/osmium/index/relations_map.hpp b/include/osmium/index/relations_map.hpp
index bb9f5a7..bc26719 100644
--- a/include/osmium/index/relations_map.hpp
+++ b/include/osmium/index/relations_map.hpp
@@ -35,10 +35,14 @@ DEALINGS IN THE SOFTWARE.
 
 #include <algorithm>
 #include <cassert>
+#include <cstddef>
 #include <cstdint>
 #include <tuple>
+#include <type_traits>
+#include <utility>
 #include <vector>
 
+#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/types.hpp>
 
@@ -125,11 +129,11 @@ namespace osmium {
                     return m_map.empty();
                 }
 
-                size_t size() const noexcept {
+                std::size_t size() const noexcept {
                     return m_map.size();
                 }
 
-                void reserve(size_t size) {
+                void reserve(std::size_t size) {
                     m_map.reserve(size);
                 }
 
@@ -172,7 +176,7 @@ namespace osmium {
 
             map_type m_map;
 
-            RelationsMapIndex(map_type&& map) :
+            explicit RelationsMapIndex(map_type&& map) :
                 m_map(std::move(map)) {
             }
 
@@ -246,7 +250,7 @@ namespace osmium {
              *
              * Complexity: Constant.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 return m_map.size();
             }
 
@@ -288,7 +292,7 @@ namespace osmium {
              *
              * Complexity: Constant.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 return m_member_to_parent.size();
             }
 
@@ -355,7 +359,7 @@ namespace osmium {
              *
              * Complexity: Constant.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 assert(m_valid && "You can't use the RelationsMap any more after calling build_index()");
                 return m_map.size();
             }
diff --git a/include/osmium/io/bzip2_compression.hpp b/include/osmium/io/bzip2_compression.hpp
index 84b2825..094062c 100644
--- a/include/osmium/io/bzip2_compression.hpp
+++ b/include/osmium/io/bzip2_compression.hpp
@@ -85,7 +85,7 @@ namespace osmium {
         namespace detail {
 
             OSMIUM_NORETURN inline void throw_bzip2_error(BZFILE* bzfile, const char* msg, int bzlib_error = 0) {
-                std::string error("bzip2 error: ");
+                std::string error{"bzip2 error: "};
                 error += msg;
                 error += ": ";
                 int errnum = bzlib_error;
@@ -94,7 +94,7 @@ namespace osmium {
                 } else {
                     error += ::BZ2_bzerror(bzfile, &errnum);
                 }
-                throw osmium::bzip2_error(error, errnum);
+                throw osmium::bzip2_error{error, errnum};
             }
 
         } // namespace detail
@@ -143,7 +143,7 @@ namespace osmium {
                             osmium::io::detail::reliable_fsync(::fileno(m_file));
                         }
                         if (fclose(m_file) != 0) {
-                            throw std::system_error(errno, std::system_category(), "Close failed");
+                            throw std::system_error{errno, std::system_category(), "Close failed"};
                         }
                     }
                     if (error != BZ_OK) {
@@ -187,7 +187,7 @@ namespace osmium {
                 if (!m_stream_end) {
                     buffer.resize(osmium::io::Decompressor::input_buffer_size);
                     int error;
-                    int nread = ::BZ2_bzRead(&error, m_bzfile, const_cast<char*>(buffer.data()), static_cast_with_assert<int>(buffer.size()));
+                    const int nread = ::BZ2_bzRead(&error, m_bzfile, const_cast<char*>(buffer.data()), static_cast_with_assert<int>(buffer.size()));
                     if (error != BZ_OK && error != BZ_STREAM_END) {
                         detail::throw_bzip2_error(m_bzfile, "read failed", error);
                     }
@@ -227,7 +227,7 @@ namespace osmium {
                     m_bzfile = nullptr;
                     if (m_file) {
                         if (fclose(m_file) != 0) {
-                            throw std::system_error(errno, std::system_category(), "Close failed");
+                            throw std::system_error{errno, std::system_category(), "Close failed"};
                         }
                     }
                     if (error != BZ_OK) {
@@ -252,10 +252,10 @@ namespace osmium {
                 m_bzstream() {
                 m_bzstream.next_in = const_cast<char*>(buffer);
                 m_bzstream.avail_in = static_cast_with_assert<unsigned int>(size);
-                int result = BZ2_bzDecompressInit(&m_bzstream, 0, 0);
+                const int result = BZ2_bzDecompressInit(&m_bzstream, 0, 0);
                 if (result != BZ_OK) {
-                    std::string message("bzip2 error: decompression init failed: ");
-                    throw bzip2_error(message, result);
+                    std::string message{"bzip2 error: decompression init failed: "};
+                    throw bzip2_error{message, result};
                 }
             }
 
@@ -275,7 +275,7 @@ namespace osmium {
                     output.resize(buffer_size);
                     m_bzstream.next_out = const_cast<char*>(output.data());
                     m_bzstream.avail_out = buffer_size;
-                    int result = BZ2_bzDecompress(&m_bzstream);
+                    const int result = BZ2_bzDecompress(&m_bzstream);
 
                     if (result != BZ_OK) {
                         m_buffer = nullptr;
@@ -283,8 +283,8 @@ namespace osmium {
                     }
 
                     if (result != BZ_OK && result != BZ_STREAM_END) {
-                        std::string message("bzip2 error: decompress failed: ");
-                        throw bzip2_error(message, result);
+                        std::string message{"bzip2 error: decompress failed: "};
+                        throw bzip2_error{message, result};
                     }
 
                     output.resize(static_cast<unsigned long>(m_bzstream.next_out - output.data()));
@@ -304,9 +304,9 @@ namespace osmium {
             // we want the register_compression() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_bzip2_compression = osmium::io::CompressionFactory::instance().register_compression(osmium::io::file_compression::bzip2,
-                [](int fd, fsync sync) { return new osmium::io::Bzip2Compressor(fd, sync); },
-                [](int fd) { return new osmium::io::Bzip2Decompressor(fd); },
-                [](const char* buffer, size_t size) { return new osmium::io::Bzip2BufferDecompressor(buffer, size); }
+                [](int fd, fsync sync) { return new osmium::io::Bzip2Compressor{fd, sync}; },
+                [](int fd) { return new osmium::io::Bzip2Decompressor{fd}; },
+                [](const char* buffer, size_t size) { return new osmium::io::Bzip2BufferDecompressor{buffer, size}; }
             );
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/compression.hpp b/include/osmium/io/compression.hpp
index 68011e8..c3b8706 100644
--- a/include/osmium/io/compression.hpp
+++ b/include/osmium/io/compression.hpp
@@ -54,7 +54,6 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/error.hpp>
 #include <osmium/io/file_compression.hpp>
 #include <osmium/io/writer_options.hpp>
-#include <osmium/util/compatibility.hpp>
 #include <osmium/util/file.hpp>
 
 namespace osmium {
@@ -77,8 +76,7 @@ namespace osmium {
                 m_fsync(sync) {
             }
 
-            virtual ~Compressor() noexcept {
-            }
+            virtual ~Compressor() noexcept = default;
 
             virtual void write(const std::string& data) = 0;
 
@@ -88,8 +86,8 @@ namespace osmium {
 
         class Decompressor {
 
-            std::atomic<size_t> m_file_size {0};
-            std::atomic<size_t> m_offset {0};
+            std::atomic<std::size_t> m_file_size{0};
+            std::atomic<std::size_t> m_offset{0};
 
         public:
 
@@ -103,26 +101,25 @@ namespace osmium {
             Decompressor(Decompressor&&) = delete;
             Decompressor& operator=(Decompressor&&) = delete;
 
-            virtual ~Decompressor() noexcept {
-            }
+            virtual ~Decompressor() noexcept = default;
 
             virtual std::string read() = 0;
 
             virtual void close() = 0;
 
-            size_t file_size() const noexcept {
+            std::size_t file_size() const noexcept {
                 return m_file_size;
             }
 
-            void set_file_size(size_t size) noexcept {
+            void set_file_size(std::size_t size) noexcept {
                 m_file_size = size;
             }
 
-            size_t offset() const noexcept {
+            std::size_t offset() const noexcept {
                 return m_offset;
             }
 
-            void set_offset(size_t offset) noexcept {
+            void set_offset(std::size_t offset) noexcept {
                 m_offset = offset;
             }
 
@@ -141,7 +138,7 @@ namespace osmium {
 
             using create_compressor_type          = std::function<osmium::io::Compressor*(int, fsync)>;
             using create_decompressor_type_fd     = std::function<osmium::io::Decompressor*(int)>;
-            using create_decompressor_type_buffer = std::function<osmium::io::Decompressor*(const char*, size_t)>;
+            using create_decompressor_type_buffer = std::function<osmium::io::Decompressor*(const char*, std::size_t)>;
 
         private:
 
@@ -187,10 +184,10 @@ namespace osmium {
                 create_decompressor_type_fd create_decompressor_fd,
                 create_decompressor_type_buffer create_decompressor_buffer) {
 
-                compression_map_type::value_type cc(compression,
+                compression_map_type::value_type cc{compression,
                                                     std::make_tuple(create_compressor,
                                                                     create_decompressor_fd,
-                                                                    create_decompressor_buffer));
+                                                                    create_decompressor_buffer)};
 
                 return m_callbacks.insert(cc).second;
             }
@@ -208,7 +205,7 @@ namespace osmium {
                 return p;
             }
 
-            std::unique_ptr<osmium::io::Decompressor> create_decompressor(osmium::io::file_compression compression, const char* buffer, size_t size) const {
+            std::unique_ptr<osmium::io::Decompressor> create_decompressor(osmium::io::file_compression compression, const char* buffer, std::size_t size) const {
                 const auto callbacks = find_callbacks(compression);
                 return std::unique_ptr<osmium::io::Decompressor>(std::get<2>(callbacks)(buffer, size));
             }
@@ -240,7 +237,7 @@ namespace osmium {
 
             void close() final {
                 if (m_fd >= 0) {
-                    int fd = m_fd;
+                    const int fd = m_fd;
                     m_fd = -1;
                     if (do_fsync()) {
                         osmium::io::detail::reliable_fsync(fd);
@@ -255,8 +252,8 @@ namespace osmium {
 
             int m_fd;
             const char *m_buffer;
-            size_t m_buffer_size;
-            size_t m_offset = 0;
+            std::size_t m_buffer_size;
+            std::size_t m_offset = 0;
 
         public:
 
@@ -267,7 +264,7 @@ namespace osmium {
                 m_buffer_size(0) {
             }
 
-            NoDecompressor(const char* buffer, size_t size) :
+            NoDecompressor(const char* buffer, std::size_t size) :
                 Decompressor(),
                 m_fd(-1),
                 m_buffer(buffer),
@@ -287,15 +284,15 @@ namespace osmium {
 
                 if (m_buffer) {
                     if (m_buffer_size != 0) {
-                        size_t size = m_buffer_size;
+                        const std::size_t size = m_buffer_size;
                         m_buffer_size = 0;
                         buffer.append(m_buffer, size);
                     }
                 } else {
                     buffer.resize(osmium::io::Decompressor::input_buffer_size);
-                    auto nread = ::read(m_fd, const_cast<char*>(buffer.data()), osmium::io::Decompressor::input_buffer_size);
+                    const auto nread = ::read(m_fd, const_cast<char*>(buffer.data()), osmium::io::Decompressor::input_buffer_size);
                     if (nread < 0) {
-                        throw std::system_error(errno, std::system_category(), "Read failed");
+                        throw std::system_error{errno, std::system_category(), "Read failed"};
                     }
                     buffer.resize(std::string::size_type(nread));
                 }
@@ -308,7 +305,7 @@ namespace osmium {
 
             void close() final {
                 if (m_fd >= 0) {
-                    int fd = m_fd;
+                    const int fd = m_fd;
                     m_fd = -1;
                     osmium::io::detail::reliable_close(fd);
                 }
@@ -321,9 +318,9 @@ namespace osmium {
             // we want the register_compression() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_no_compression = osmium::io::CompressionFactory::instance().register_compression(osmium::io::file_compression::none,
-                [](int fd, fsync sync) { return new osmium::io::NoCompressor(fd, sync); },
-                [](int fd) { return new osmium::io::NoDecompressor(fd); },
-                [](const char* buffer, size_t size) { return new osmium::io::NoDecompressor(buffer, size); }
+                [](int fd, fsync sync) { return new osmium::io::NoCompressor{fd, sync}; },
+                [](int fd) { return new osmium::io::NoDecompressor{fd}; },
+                [](const char* buffer, std::size_t size) { return new osmium::io::NoDecompressor{buffer, size}; }
             );
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/debug_output_format.hpp b/include/osmium/io/detail/debug_output_format.hpp
index 7e2f07e..f7ce7c8 100644
--- a/include/osmium/io/detail/debug_output_format.hpp
+++ b/include/osmium/io/detail/debug_output_format.hpp
@@ -282,15 +282,16 @@ namespace osmium {
 
                 void write_box(const osmium::Box& box) {
                     write_fieldname("box l/b/r/t");
-                    if (!box) {
+                    if (box.bottom_left().is_undefined() &&
+                        box.top_right().is_undefined()) {
                         write_error("BOX NOT SET!\n");
                         return;
                     }
                     const auto& bl = box.bottom_left();
                     const auto& tr = box.top_right();
-                    bl.as_string(std::back_inserter(*m_out));
+                    bl.as_string_without_check(std::back_inserter(*m_out));
                     *m_out += ' ';
-                    tr.as_string(std::back_inserter(*m_out));
+                    tr.as_string_without_check(std::back_inserter(*m_out));
                     if (!box.valid()) {
                         write_error(" INVALID BOX!");
                     }
@@ -520,8 +521,8 @@ namespace osmium {
 
             public:
 
-                DebugOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) :
-                    OutputFormat(output_queue),
+                DebugOutputFormat(osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) :
+                    OutputFormat(pool, output_queue),
                     m_options() {
                     m_options.add_metadata   = file.is_not_false("add_metadata");
                     m_options.use_color      = file.is_true("color");
@@ -576,7 +577,7 @@ namespace osmium {
                 }
 
                 void write_buffer(osmium::memory::Buffer&& buffer) final {
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(DebugOutputBlock{std::move(buffer), m_options}));
+                    m_output_queue.push(m_pool.submit(DebugOutputBlock{std::move(buffer), m_options}));
                 }
 
             }; // class DebugOutputFormat
@@ -584,8 +585,8 @@ namespace osmium {
             // we want the register_output_format() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_debug_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::debug,
-                [](const osmium::io::File& file, future_string_queue_type& output_queue) {
-                    return new osmium::io::detail::DebugOutputFormat(file, output_queue);
+                [](osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) {
+                    return new osmium::io::detail::DebugOutputFormat(pool, file, output_queue);
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/input_format.hpp b/include/osmium/io/detail/input_format.hpp
index 8651a35..442c717 100644
--- a/include/osmium/io/detail/input_format.hpp
+++ b/include/osmium/io/detail/input_format.hpp
@@ -48,6 +48,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/entity_bits.hpp>
+#include <osmium/thread/pool.hpp>
 
 namespace osmium {
 
@@ -56,6 +57,7 @@ namespace osmium {
         namespace detail {
 
             struct parser_arguments {
+                osmium::thread::Pool& pool;
                 future_string_queue_type& input_queue;
                 future_buffer_queue_type& output_queue;
                 std::promise<osmium::io::Header>& header_promise;
@@ -65,6 +67,7 @@ namespace osmium {
 
             class Parser {
 
+                osmium::thread::Pool& m_pool;
                 future_buffer_queue_type& m_output_queue;
                 std::promise<osmium::io::Header>& m_header_promise;
                 queue_wrapper<std::string> m_input_queue;
@@ -74,6 +77,10 @@ namespace osmium {
 
             protected:
 
+                osmium::thread::Pool& get_pool() {
+                    return m_pool;
+                }
+
                 std::string get_input() {
                     return m_input_queue.pop();
                 }
@@ -121,7 +128,8 @@ namespace osmium {
 
             public:
 
-                Parser(parser_arguments& args) :
+                explicit Parser(parser_arguments& args) :
+                    m_pool(args.pool),
                     m_output_queue(args.output_queue),
                     m_header_promise(args.header_promise),
                     m_input_queue(args.input_queue),
diff --git a/include/osmium/io/detail/o5m_input_format.hpp b/include/osmium/io/detail/o5m_input_format.hpp
index fa47ed0..e0b3fa8 100644
--- a/include/osmium/io/detail/o5m_input_format.hpp
+++ b/include/osmium/io/detail/o5m_input_format.hpp
@@ -121,7 +121,7 @@ namespace osmium {
                     current_entry = 0;
                 }
 
-                void add(const char* string, size_t size) {
+                void add(const char* string, std::size_t size) {
                     if (m_table.empty()) {
                         m_table.resize(entry_size * number_of_entries);
                     }
@@ -162,7 +162,7 @@ namespace osmium {
                     return protozero::decode_zigzag64(protozero::decode_varint(data, end));
                 }
 
-                bool ensure_bytes_available(size_t need_bytes) {
+                bool ensure_bytes_available(std::size_t need_bytes) {
                     if ((m_end - m_data) >= long(need_bytes)) {
                         return true;
                     }
@@ -174,7 +174,7 @@ namespace osmium {
                     m_input.erase(0, m_data - m_input.data());
 
                     while (m_input.size() < need_bytes) {
-                        std::string data = get_input();
+                        const std::string data{get_input()};
                         if (input_done()) {
                             return false;
                         }
@@ -589,7 +589,7 @@ namespace osmium {
 
             public:
 
-                O5mParser(parser_arguments& args) :
+                explicit O5mParser(parser_arguments& args) :
                     Parser(args),
                     m_header(),
                     m_buffer(buffer_size),
diff --git a/include/osmium/io/detail/opl_input_format.hpp b/include/osmium/io/detail/opl_input_format.hpp
index fd556e6..0d7a62d 100644
--- a/include/osmium/io/detail/opl_input_format.hpp
+++ b/include/osmium/io/detail/opl_input_format.hpp
@@ -33,19 +33,16 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstdlib>
-#include <future>
+#include <cstdint>
 #include <memory>
 #include <string>
 #include <utility>
 
 #include <osmium/io/detail/input_format.hpp>
 #include <osmium/io/detail/opl_parser_functions.hpp>
-#include <osmium/io/detail/queue_util.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
-#include <osmium/osm/entity_bits.hpp>
 #include <osmium/thread/util.hpp>
 
 namespace osmium {
@@ -79,7 +76,7 @@ namespace osmium {
 
             public:
 
-                OPLParser(parser_arguments& args) :
+                explicit OPLParser(parser_arguments& args) :
                     Parser(args) {
                     set_header_value(osmium::io::Header{});
                 }
@@ -91,7 +88,7 @@ namespace osmium {
 
                     std::string rest;
                     while (!input_done()) {
-                        std::string input = get_input();
+                        std::string input{get_input()};
                         std::string::size_type ppos = 0;
 
                         if (!rest.empty()) {
diff --git a/include/osmium/io/detail/opl_output_format.hpp b/include/osmium/io/detail/opl_output_format.hpp
index bfcbcb1..d1fb2e5 100644
--- a/include/osmium/io/detail/opl_output_format.hpp
+++ b/include/osmium/io/detail/opl_output_format.hpp
@@ -141,14 +141,15 @@ namespace osmium {
                 }
 
                 void write_location(const osmium::Location& location, const char x, const char y) {
+                    const bool not_undefined = !location.is_undefined();
                     *m_out += ' ';
                     *m_out += x;
-                    if (location) {
+                    if (not_undefined) {
                         osmium::detail::append_location_coordinate_to_string(std::back_inserter(*m_out), location.x());
                     }
                     *m_out += ' ';
                     *m_out += y;
-                    if (location) {
+                    if (not_undefined) {
                         osmium::detail::append_location_coordinate_to_string(std::back_inserter(*m_out), location.y());
                     }
                 }
@@ -283,8 +284,8 @@ namespace osmium {
 
             public:
 
-                OPLOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) :
-                    OutputFormat(output_queue),
+                OPLOutputFormat(osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) :
+                    OutputFormat(pool, output_queue),
                     m_options() {
                     m_options.add_metadata      = file.is_not_false("add_metadata");
                     m_options.locations_on_ways = file.is_true("locations_on_ways");
@@ -297,7 +298,7 @@ namespace osmium {
                 ~OPLOutputFormat() noexcept final = default;
 
                 void write_buffer(osmium::memory::Buffer&& buffer) final {
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(OPLOutputBlock{std::move(buffer), m_options}));
+                    m_output_queue.push(m_pool.submit(OPLOutputBlock{std::move(buffer), m_options}));
                 }
 
             }; // class OPLOutputFormat
@@ -305,8 +306,8 @@ namespace osmium {
             // we want the register_output_format() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_opl_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::opl,
-                [](const osmium::io::File& file, future_string_queue_type& output_queue) {
-                    return new osmium::io::detail::OPLOutputFormat(file, output_queue);
+                [](osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) {
+                    return new osmium::io::detail::OPLOutputFormat(pool, file, output_queue);
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/opl_parser_functions.hpp b/include/osmium/io/detail/opl_parser_functions.hpp
index 622a7ca..f21145b 100644
--- a/include/osmium/io/detail/opl_parser_functions.hpp
+++ b/include/osmium/io/detail/opl_parser_functions.hpp
@@ -81,7 +81,7 @@ namespace osmium {
         }
 
         explicit opl_error(const char* what, const char* d = nullptr) :
-            io_error(std::string("OPL error: ") + what),
+            io_error(std::string{"OPL error: "} + what),
             data(d),
             msg("OPL error: ") {
             msg.append(what);
@@ -291,7 +291,7 @@ namespace osmium {
                     ++*data;
                     return;
                 }
-                std::string msg = "expected '";
+                std::string msg{"expected '"};
                 msg += c;
                 msg += "'";
                 throw opl_error{msg, *data};
@@ -599,8 +599,7 @@ namespace osmium {
 
                 const char* tags_begin = nullptr;
 
-                osmium::Location location1;
-                osmium::Location location2;
+                osmium::Box box;
                 std::string user;
                 while (**data) {
                     opl_parse_space(data);
@@ -630,22 +629,22 @@ namespace osmium {
                             break;
                         case 'x':
                             if (opl_non_empty(*data)) {
-                                location1.set_lon_partial(data);
+                                box.bottom_left().set_lon_partial(data);
                             }
                             break;
                         case 'y':
                             if (opl_non_empty(*data)) {
-                                location1.set_lat_partial(data);
+                                box.bottom_left().set_lat_partial(data);
                             }
                             break;
                         case 'X':
                             if (opl_non_empty(*data)) {
-                                location2.set_lon_partial(data);
+                                box.top_right().set_lon_partial(data);
                             }
                             break;
                         case 'Y':
                             if (opl_non_empty(*data)) {
-                                location2.set_lat_partial(data);
+                                box.top_right().set_lat_partial(data);
                             }
                             break;
                         case 'T':
@@ -661,13 +660,7 @@ namespace osmium {
 
                 }
 
-                if (location1.valid() && location2.valid()) {
-                    osmium::Box box;
-                    box.extend(location1);
-                    box.extend(location2);
-                    builder.set_bounds(box);
-                }
-
+                builder.set_bounds(box);
                 builder.set_user(user);
 
                 if (tags_begin) {
diff --git a/include/osmium/io/detail/output_format.hpp b/include/osmium/io/detail/output_format.hpp
index ccb0efd..5fe8bd9 100644
--- a/include/osmium/io/detail/output_format.hpp
+++ b/include/osmium/io/detail/output_format.hpp
@@ -46,6 +46,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/memory/buffer.hpp>
+#include <osmium/thread/pool.hpp>
 
 namespace osmium {
 
@@ -107,6 +108,7 @@ namespace osmium {
 
             protected:
 
+                osmium::thread::Pool& m_pool;
                 future_string_queue_type& m_output_queue;
 
                 /**
@@ -119,7 +121,8 @@ namespace osmium {
 
             public:
 
-                explicit OutputFormat(future_string_queue_type& output_queue) :
+                OutputFormat(osmium::thread::Pool& pool, future_string_queue_type& output_queue) :
+                    m_pool(pool),
                     m_output_queue(output_queue) {
                 }
 
@@ -152,7 +155,7 @@ namespace osmium {
 
             public:
 
-                using create_output_type = std::function<osmium::io::detail::OutputFormat*(const osmium::io::File&, future_string_queue_type&)>;
+                using create_output_type = std::function<osmium::io::detail::OutputFormat*(osmium::thread::Pool&, const osmium::io::File&, future_string_queue_type&)>;
 
             private:
 
@@ -178,18 +181,18 @@ namespace osmium {
                     return true;
                 }
 
-                std::unique_ptr<osmium::io::detail::OutputFormat> create_output(const osmium::io::File& file, future_string_queue_type& output_queue) {
-                    auto it = m_callbacks.find(file.format());
+                std::unique_ptr<osmium::io::detail::OutputFormat> create_output(osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) {
+                    const auto it = m_callbacks.find(file.format());
                     if (it != m_callbacks.end()) {
-                        return std::unique_ptr<osmium::io::detail::OutputFormat>((it->second)(file, output_queue));
+                        return std::unique_ptr<osmium::io::detail::OutputFormat>((it->second)(pool, file, output_queue));
                     }
 
-                    throw unsupported_file_format_error(
-                                std::string("Can not open file '") +
+                    throw unsupported_file_format_error{
+                                std::string{"Can not open file '"} +
                                 file.filename() +
                                 "' with type '" +
                                 as_string(file.format()) +
-                                "'. No support for writing this format in this program.");
+                                "'. No support for writing this format in this program."};
                 }
 
             }; // class OutputFormatFactory
diff --git a/include/osmium/io/detail/pbf_decoder.hpp b/include/osmium/io/detail/pbf_decoder.hpp
index e47cff6..6b05301 100644
--- a/include/osmium/io/detail/pbf_decoder.hpp
+++ b/include/osmium/io/detail/pbf_decoder.hpp
@@ -99,21 +99,21 @@ namespace osmium {
 
                 void decode_stringtable(const data_view& data) {
                     if (!m_stringtable.empty()) {
-                        throw osmium::pbf_error("more than one stringtable in pbf file");
+                        throw osmium::pbf_error{"more than one stringtable in pbf file"};
                     }
 
-                    protozero::pbf_message<OSMFormat::StringTable> pbf_string_table(data);
+                    protozero::pbf_message<OSMFormat::StringTable> pbf_string_table{data};
                     while (pbf_string_table.next(OSMFormat::StringTable::repeated_bytes_s)) {
                         const auto str_view = pbf_string_table.get_view();
                         if (str_view.size() > osmium::max_osm_string_length) {
-                            throw osmium::pbf_error("overlong string in string table");
+                            throw osmium::pbf_error{"overlong string in string table"};
                         }
                         m_stringtable.emplace_back(str_view.data(), osmium::string_size_type(str_view.size()));
                     }
                 }
 
                 void decode_primitive_block_metadata() {
-                    protozero::pbf_message<OSMFormat::PrimitiveBlock> pbf_primitive_block(m_data);
+                    protozero::pbf_message<OSMFormat::PrimitiveBlock> pbf_primitive_block{m_data};
                     while (pbf_primitive_block.next()) {
                         switch (pbf_primitive_block.tag()) {
                             case OSMFormat::PrimitiveBlock::required_StringTable_stringtable:
@@ -138,7 +138,7 @@ namespace osmium {
                 }
 
                 void decode_primitive_block_data() {
-                    protozero::pbf_message<OSMFormat::PrimitiveBlock> pbf_primitive_block(m_data);
+                    protozero::pbf_message<OSMFormat::PrimitiveBlock> pbf_primitive_block{m_data};
                     while (pbf_primitive_block.next(OSMFormat::PrimitiveBlock::repeated_PrimitiveGroup_primitivegroup)) {
                         protozero::pbf_message<OSMFormat::PrimitiveGroup> pbf_primitive_group = pbf_primitive_block.get_message();
                         while (pbf_primitive_group.next()) {
@@ -187,16 +187,16 @@ namespace osmium {
                 }
 
                 osm_string_len_type decode_info(const data_view& data, osmium::OSMObject& object) {
-                    osm_string_len_type user = std::make_pair("", 0);
+                    osm_string_len_type user{"", 0};
 
-                    protozero::pbf_message<OSMFormat::Info> pbf_info(data);
+                    protozero::pbf_message<OSMFormat::Info> pbf_info{data};
                     while (pbf_info.next()) {
                         switch (pbf_info.tag()) {
                             case OSMFormat::Info::optional_int32_version:
                                 {
                                     const auto version = pbf_info.get_int32();
                                     if (version < 0) {
-                                        throw osmium::pbf_error("object version must not be negative");
+                                        throw osmium::pbf_error{"object version must not be negative"};
                                     }
                                     object.set_version(static_cast_with_assert<object_version_type>(version));
                                 }
@@ -208,7 +208,7 @@ namespace osmium {
                                 {
                                     const auto changeset_id = pbf_info.get_int64();
                                     if (changeset_id < 0) {
-                                        throw osmium::pbf_error("object changeset_id must not be negative");
+                                        throw osmium::pbf_error{"object changeset_id must not be negative"};
                                     }
                                     object.set_changeset(static_cast_with_assert<changeset_id_type>(changeset_id));
                                 }
@@ -240,7 +240,7 @@ namespace osmium {
                         while (kit != keys.end()) {
                             if (vit == vals.end()) {
                                 // this is against the spec, must have same number of elements
-                                throw osmium::pbf_error("PBF format error");
+                                throw osmium::pbf_error{"PBF format error"};
                             }
                             const auto& k = m_stringtable.at(*kit++);
                             const auto& v = m_stringtable.at(*vit++);
@@ -262,9 +262,9 @@ namespace osmium {
                     int64_t lon = std::numeric_limits<int64_t>::max();
                     int64_t lat = std::numeric_limits<int64_t>::max();
 
-                    osm_string_len_type user = { "", 0 };
+                    osm_string_len_type user{"", 0};
 
-                    protozero::pbf_message<OSMFormat::Node> pbf_node(data);
+                    protozero::pbf_message<OSMFormat::Node> pbf_node{data};
                     while (pbf_node.next()) {
                         switch (pbf_node.tag()) {
                             case OSMFormat::Node::required_sint64_id:
@@ -297,12 +297,12 @@ namespace osmium {
                     if (node.visible()) {
                         if (lon == std::numeric_limits<int64_t>::max() ||
                             lat == std::numeric_limits<int64_t>::max()) {
-                            throw osmium::pbf_error("illegal coordinate format");
+                            throw osmium::pbf_error{"illegal coordinate format"};
                         }
-                        node.set_location(osmium::Location(
+                        node.set_location(osmium::Location{
                                 convert_pbf_coordinate(lon),
                                 convert_pbf_coordinate(lat)
-                        ));
+                        });
                     }
 
                     builder.set_user(user.first, user.second);
@@ -319,9 +319,9 @@ namespace osmium {
                     protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lats;
                     protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> lons;
 
-                    osm_string_len_type user = { "", 0 };
+                    osm_string_len_type user{"", 0};
 
-                    protozero::pbf_message<OSMFormat::Way> pbf_way(data);
+                    protozero::pbf_message<OSMFormat::Way> pbf_way{data};
                     while (pbf_way.next()) {
                         switch (pbf_way.tag()) {
                             case OSMFormat::Way::required_int64_id:
@@ -391,9 +391,9 @@ namespace osmium {
                     protozero::iterator_range<protozero::pbf_reader::const_sint64_iterator> refs;
                     protozero::iterator_range<protozero::pbf_reader::const_int32_iterator> types;
 
-                    osm_string_len_type user = { "", 0 };
+                    osm_string_len_type user{"", 0};
 
-                    protozero::pbf_message<OSMFormat::Relation> pbf_relation(data);
+                    protozero::pbf_message<OSMFormat::Relation> pbf_relation{data};
                     while (pbf_relation.next()) {
                         switch (pbf_relation.tag()) {
                             case OSMFormat::Relation::required_int64_id:
@@ -435,7 +435,7 @@ namespace osmium {
                             const auto& r = m_stringtable.at(roles.front());
                             const int type = types.front();
                             if (type < 0 || type > 2) {
-                                throw osmium::pbf_error("unknown relation member type");
+                                throw osmium::pbf_error{"unknown relation member type"};
                             }
                             rml_builder.add_member(
                                 osmium::item_type(type + 1),
@@ -457,7 +457,7 @@ namespace osmium {
                     while (it != last && *it != 0) {
                         const auto& k = m_stringtable.at(*it++);
                         if (it == last) {
-                            throw osmium::pbf_error("PBF format error"); // this is against the spec, keys/vals must come in pairs
+                            throw osmium::pbf_error{"PBF format error"}; // this is against the spec, keys/vals must come in pairs
                         }
                         const auto& v = m_stringtable.at(*it++);
                         tl_builder.add_tag(k.first, k.second, v.first, v.second);
@@ -475,7 +475,7 @@ namespace osmium {
 
                     protozero::iterator_range<protozero::pbf_reader::const_int32_iterator>  tags;
 
-                    protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes(data);
+                    protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes{data};
                     while (pbf_dense_nodes.next()) {
                         switch (pbf_dense_nodes.tag()) {
                             case OSMFormat::DenseNodes::packed_sint64_id:
@@ -505,7 +505,7 @@ namespace osmium {
                         if (lons.empty() ||
                             lats.empty()) {
                             // this is against the spec, must have same number of elements
-                            throw osmium::pbf_error("PBF format error");
+                            throw osmium::pbf_error{"PBF format error"};
                         }
 
                         osmium::builder::NodeBuilder builder{m_buffer};
@@ -547,7 +547,7 @@ namespace osmium {
                     protozero::iterator_range<protozero::pbf_reader::const_sint32_iterator> user_sids;
                     protozero::iterator_range<protozero::pbf_reader::const_int32_iterator>  visibles;
 
-                    protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes(data);
+                    protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes{data};
                     while (pbf_dense_nodes.next()) {
                         switch (pbf_dense_nodes.tag()) {
                             case OSMFormat::DenseNodes::packed_sint64_id:
@@ -556,7 +556,7 @@ namespace osmium {
                             case OSMFormat::DenseNodes::optional_DenseInfo_denseinfo:
                                 {
                                     has_info = true;
-                                    protozero::pbf_message<OSMFormat::DenseInfo> pbf_dense_info = pbf_dense_nodes.get_message();
+                                    protozero::pbf_message<OSMFormat::DenseInfo> pbf_dense_info{pbf_dense_nodes.get_message()};
                                     while (pbf_dense_info.next()) {
                                         switch (pbf_dense_info.tag()) {
                                             case OSMFormat::DenseInfo::packed_int32_version:
@@ -612,7 +612,7 @@ namespace osmium {
                         if (lons.empty() ||
                             lats.empty()) {
                             // this is against the spec, must have same number of elements
-                            throw osmium::pbf_error("PBF format error");
+                            throw osmium::pbf_error{"PBF format error"};
                         }
 
                         bool visible = true;
@@ -630,20 +630,20 @@ namespace osmium {
                                 uids.empty() ||
                                 user_sids.empty()) {
                                 // this is against the spec, must have same number of elements
-                                throw osmium::pbf_error("PBF format error");
+                                throw osmium::pbf_error{"PBF format error"};
                             }
 
                             const auto version = versions.front();
                             versions.drop_front();
                             if (version < 0) {
-                                throw osmium::pbf_error("object version must not be negative");
+                                throw osmium::pbf_error{"object version must not be negative"};
                             }
                             node.set_version(static_cast<osmium::object_version_type>(version));
 
                             const auto changeset_id = dense_changeset.update(changesets.front());
                             changesets.drop_front();
                             if (changeset_id < 0) {
-                                throw osmium::pbf_error("object changeset_id must not be negative");
+                                throw osmium::pbf_error{"object changeset_id must not be negative"};
                             }
                             node.set_changeset(static_cast<osmium::changeset_id_type>(changeset_id));
 
@@ -655,7 +655,7 @@ namespace osmium {
                             if (has_visibles) {
                                 if (visibles.empty()) {
                                     // this is against the spec, must have same number of elements
-                                    throw osmium::pbf_error("PBF format error");
+                                    throw osmium::pbf_error{"PBF format error"};
                                 }
                                 visible = (visibles.front() != 0);
                                 visibles.drop_front();
@@ -674,10 +674,10 @@ namespace osmium {
                         const auto lat = dense_latitude.update(lats.front());
                         lats.drop_front();
                         if (visible) {
-                            builder.object().set_location(osmium::Location(
+                            builder.object().set_location(osmium::Location{
                                     convert_pbf_coordinate(lon),
                                     convert_pbf_coordinate(lat)
-                            ));
+                            });
                         }
 
                         if (tag_it != tags.end()) {
@@ -708,7 +708,7 @@ namespace osmium {
                         decode_primitive_block_metadata();
                         decode_primitive_block_data();
                     } catch (const std::out_of_range&) {
-                        throw osmium::pbf_error("string id out of range");
+                        throw osmium::pbf_error{"string id out of range"};
                     }
 
                     return std::move(m_buffer);
@@ -720,30 +720,30 @@ namespace osmium {
                 int32_t raw_size = 0;
                 protozero::data_view zlib_data;
 
-                protozero::pbf_message<FileFormat::Blob> pbf_blob(blob_data);
+                protozero::pbf_message<FileFormat::Blob> pbf_blob{blob_data};
                 while (pbf_blob.next()) {
                     switch (pbf_blob.tag()) {
                         case FileFormat::Blob::optional_bytes_raw:
                             {
-                                auto data_len = pbf_blob.get_view();
+                                const auto data_len = pbf_blob.get_view();
                                 if (data_len.size() > max_uncompressed_blob_size) {
-                                    throw osmium::pbf_error("illegal blob size");
+                                    throw osmium::pbf_error{"illegal blob size"};
                                 }
                                 return data_len;
                             }
                         case FileFormat::Blob::optional_int32_raw_size:
                             raw_size = pbf_blob.get_int32();
                             if (raw_size <= 0 || uint32_t(raw_size) > max_uncompressed_blob_size) {
-                                throw osmium::pbf_error("illegal blob size");
+                                throw osmium::pbf_error{"illegal blob size"};
                             }
                             break;
                         case FileFormat::Blob::optional_bytes_zlib_data:
                             zlib_data = pbf_blob.get_view();
                             break;
                         case FileFormat::Blob::optional_bytes_lzma_data:
-                            throw osmium::pbf_error("lzma blobs not implemented");
+                            throw osmium::pbf_error{"lzma blobs not implemented"};
                         default:
-                            throw osmium::pbf_error("unknown compression");
+                            throw osmium::pbf_error{"unknown compression"};
                     }
                 }
 
@@ -756,7 +756,7 @@ namespace osmium {
                     );
                 }
 
-                throw osmium::pbf_error("blob contains no data");
+                throw osmium::pbf_error{"blob contains no data"};
             }
 
             inline osmium::Box decode_header_bbox(const data_view& data) {
@@ -765,7 +765,7 @@ namespace osmium {
                     int64_t top    = std::numeric_limits<int64_t>::max();
                     int64_t bottom = std::numeric_limits<int64_t>::max();
 
-                    protozero::pbf_message<OSMFormat::HeaderBBox> pbf_header_bbox(data);
+                    protozero::pbf_message<OSMFormat::HeaderBBox> pbf_header_bbox{data};
                     while (pbf_header_bbox.next()) {
                         switch (pbf_header_bbox.tag()) {
                             case OSMFormat::HeaderBBox::required_sint64_left:
@@ -789,7 +789,7 @@ namespace osmium {
                         right  == std::numeric_limits<int64_t>::max() ||
                         top    == std::numeric_limits<int64_t>::max() ||
                         bottom == std::numeric_limits<int64_t>::max()) {
-                        throw osmium::pbf_error("invalid bbox");
+                        throw osmium::pbf_error{"invalid bbox"};
                     }
 
                     osmium::Box box;
@@ -803,7 +803,7 @@ namespace osmium {
                 osmium::io::Header header;
                 int i = 0;
 
-                protozero::pbf_message<OSMFormat::HeaderBlock> pbf_header_block(data);
+                protozero::pbf_message<OSMFormat::HeaderBlock> pbf_header_block{data};
                 while (pbf_header_block.next()) {
                     switch (pbf_header_block.tag()) {
                         case OSMFormat::HeaderBlock::optional_HeaderBBox_bbox:
@@ -819,9 +819,9 @@ namespace osmium {
                                 } else if (!std::strncmp("HistoricalInformation", feature.data(), feature.size())) {
                                     header.set_has_multiple_object_versions(true);
                                 } else {
-                                    std::string msg("required feature not supported: ");
+                                    std::string msg{"required feature not supported: "};
                                     msg.append(feature.data(), feature.size());
-                                    throw osmium::pbf_error(msg);
+                                    throw osmium::pbf_error{msg};
                                 }
                             }
                             break;
@@ -833,7 +833,7 @@ namespace osmium {
                             break;
                         case OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp:
                             {
-                                const auto timestamp = osmium::Timestamp(pbf_header_block.get_int64()).to_iso();
+                                const auto timestamp = osmium::Timestamp{pbf_header_block.get_int64()}.to_iso();
                                 header.set("osmosis_replication_timestamp", timestamp);
                                 header.set("timestamp", timestamp);
                             }
@@ -889,7 +889,7 @@ namespace osmium {
 
                 osmium::memory::Buffer operator()() {
                     std::string output;
-                    PBFPrimitiveBlockDecoder decoder(decode_blob(*m_input_buffer, output), m_read_types, m_read_metadata);
+                    PBFPrimitiveBlockDecoder decoder{decode_blob(*m_input_buffer, output), m_read_types, m_read_metadata};
                     return decoder();
                 }
 
diff --git a/include/osmium/io/detail/pbf_input_format.hpp b/include/osmium/io/detail/pbf_input_format.hpp
index b5cd058..1ab4595 100644
--- a/include/osmium/io/detail/pbf_input_format.hpp
+++ b/include/osmium/io/detail/pbf_input_format.hpp
@@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE.
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
-#include <future>
 #include <memory>
 #include <string>
 #include <type_traits>
@@ -50,7 +49,6 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
 #include <osmium/io/detail/pbf_decoder.hpp>
 #include <osmium/io/detail/protobuf_tags.hpp>
-#include <osmium/io/detail/queue_util.hpp>
 #include <osmium/io/file_format.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/osm/entity_bits.hpp>
@@ -77,14 +75,14 @@ namespace osmium {
                  */
                 std::string read_from_input_queue(size_t size) {
                     while (m_input_buffer.size() < size) {
-                        const std::string new_data = get_input();
+                        const std::string new_data{get_input()};
                         if (input_done()) {
-                            throw osmium::pbf_error("truncated data (EOF encountered)");
+                            throw osmium::pbf_error{"truncated data (EOF encountered)"};
                         }
                         m_input_buffer += new_data;
                     }
 
-                    std::string output { m_input_buffer.substr(size) };
+                    std::string output{m_input_buffer.substr(size)};
                     m_input_buffer.resize(size);
 
                     using std::swap;
@@ -101,7 +99,7 @@ namespace osmium {
                     uint32_t size_in_network_byte_order;
 
                     try {
-                        const std::string input_data = read_from_input_queue(sizeof(size_in_network_byte_order));
+                        const std::string input_data{read_from_input_queue(sizeof(size_in_network_byte_order))};
                         size_in_network_byte_order = *reinterpret_cast<const uint32_t*>(input_data.data());
                     } catch (const osmium::pbf_error&) {
                         return 0; // EOF
@@ -109,7 +107,7 @@ namespace osmium {
 
                     const uint32_t size = ntohl(size_in_network_byte_order);
                     if (size > static_cast<uint32_t>(max_blob_header_size)) {
-                        throw osmium::pbf_error("invalid BlobHeader size (> max_blob_header_size)");
+                        throw osmium::pbf_error{"invalid BlobHeader size (> max_blob_header_size)"};
                     }
 
                     return size;
@@ -137,11 +135,11 @@ namespace osmium {
                     }
 
                     if (blob_header_datasize == 0) {
-                        throw osmium::pbf_error("PBF format error: BlobHeader.datasize missing or zero.");
+                        throw osmium::pbf_error{"PBF format error: BlobHeader.datasize missing or zero."};
                     }
 
                     if (std::strncmp(expected_type, blob_header_type.data(), blob_header_type.size())) {
-                        throw osmium::pbf_error("blob does not have expected type (OSMHeader in first blob, OSMData in following blobs)");
+                        throw osmium::pbf_error{"blob does not have expected type (OSMHeader in first blob, OSMData in following blobs)"};
                     }
 
                     return blob_header_datasize;
@@ -155,35 +153,34 @@ namespace osmium {
                         return 0;
                     }
 
-                    const std::string blob_header = read_from_input_queue(size);
+                    const std::string blob_header{read_from_input_queue(size)};
 
                     return decode_blob_header(protozero::pbf_message<FileFormat::BlobHeader>(blob_header), expected_type);
                 }
 
                 std::string read_from_input_queue_with_check(size_t size) {
                     if (size > max_uncompressed_blob_size) {
-                        throw osmium::pbf_error(std::string("invalid blob size: " +
-                                                std::to_string(size)));
+                        throw osmium::pbf_error{std::string{"invalid blob size: "} +
+                                                std::to_string(size)};
                     }
                     return read_from_input_queue(size);
                 }
 
                 // Parse the header in the PBF OSMHeader blob.
                 void parse_header_blob() {
-                    osmium::io::Header header;
                     const auto size = check_type_and_get_blob_size("OSMHeader");
-                    header = decode_header(read_from_input_queue_with_check(size));
+                    osmium::io::Header header{decode_header(read_from_input_queue_with_check(size))};
                     set_header_value(header);
                 }
 
                 void parse_data_blobs() {
                     while (const auto size = check_type_and_get_blob_size("OSMData")) {
-                        std::string input_buffer = read_from_input_queue_with_check(size);
+                        std::string input_buffer{read_from_input_queue_with_check(size)};
 
                         PBFDataBlobDecoder data_blob_parser{std::move(input_buffer), read_types(), read_metadata()};
 
                         if (osmium::config::use_pool_threads_for_pbf_parsing()) {
-                            send_to_output_queue(osmium::thread::Pool::instance().submit(std::move(data_blob_parser)));
+                            send_to_output_queue(get_pool().submit(std::move(data_blob_parser)));
                         } else {
                             send_to_output_queue(data_blob_parser());
                         }
@@ -192,7 +189,7 @@ namespace osmium {
 
             public:
 
-                PBFParser(parser_arguments& args) :
+                explicit PBFParser(parser_arguments& args) :
                     Parser(args),
                     m_input_buffer() {
                 }
diff --git a/include/osmium/io/detail/pbf_output_format.hpp b/include/osmium/io/detail/pbf_output_format.hpp
index 00b364f..55cd0ea 100644
--- a/include/osmium/io/detail/pbf_output_format.hpp
+++ b/include/osmium/io/detail/pbf_output_format.hpp
@@ -37,10 +37,10 @@ DEALINGS IN THE SOFTWARE.
 #include <cmath>
 #include <cstdint>
 #include <cstdlib>
-#include <iterator>
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <protozero/pbf_builder.hpp>
 #include <protozero/pbf_writer.hpp>
@@ -170,7 +170,7 @@ namespace osmium {
                     assert(m_msg.size() <= max_uncompressed_blob_size);
 
                     std::string blob_data;
-                    protozero::pbf_builder<FileFormat::Blob> pbf_blob(blob_data);
+                    protozero::pbf_builder<FileFormat::Blob> pbf_blob{blob_data};
 
                     if (m_use_compression) {
                         pbf_blob.add_int32(FileFormat::Blob::optional_int32_raw_size, int32_t(m_msg.size()));
@@ -180,12 +180,12 @@ namespace osmium {
                     }
 
                     std::string blob_header_data;
-                    protozero::pbf_builder<FileFormat::BlobHeader> pbf_blob_header(blob_header_data);
+                    protozero::pbf_builder<FileFormat::BlobHeader> pbf_blob_header{blob_header_data};
 
                     pbf_blob_header.add_string(FileFormat::BlobHeader::required_string_type, m_blob_type == pbf_blob_type::data ? "OSMData" : "OSMHeader");
                     pbf_blob_header.add_int32(FileFormat::BlobHeader::required_int32_datasize, static_cast_with_assert<int32_t>(blob_data.size()));
 
-                    uint32_t sz = htonl(static_cast_with_assert<uint32_t>(blob_header_data.size()));
+                    const uint32_t sz = htonl(static_cast_with_assert<uint32_t>(blob_header_data.size()));
 
                     // write to output: the 4-byte BlobHeader-Size followed by the BlobHeader followed by the Blob
                     std::string output;
@@ -269,7 +269,7 @@ namespace osmium {
                     m_delta_lon.clear();
                 }
 
-                size_t size() const {
+                std::size_t size() const {
                     return m_ids.size() * 3 * sizeof(int64_t);
                 }
 
@@ -299,12 +299,12 @@ namespace osmium {
 
                 std::string serialize() const {
                     std::string data;
-                    protozero::pbf_builder<OSMFormat::DenseNodes> pbf_dense_nodes(data);
+                    protozero::pbf_builder<OSMFormat::DenseNodes> pbf_dense_nodes{data};
 
                     pbf_dense_nodes.add_packed_sint64(OSMFormat::DenseNodes::packed_sint64_id, m_ids.cbegin(), m_ids.cend());
 
                     if (m_options.add_metadata) {
-                        protozero::pbf_builder<OSMFormat::DenseInfo> pbf_dense_info(pbf_dense_nodes, OSMFormat::DenseNodes::optional_DenseInfo_denseinfo);
+                        protozero::pbf_builder<OSMFormat::DenseInfo> pbf_dense_info{pbf_dense_nodes, OSMFormat::DenseNodes::optional_DenseInfo_denseinfo};
                         pbf_dense_info.add_packed_int32(OSMFormat::DenseInfo::packed_int32_version, m_versions.cbegin(), m_versions.cend());
                         pbf_dense_info.add_packed_sint64(OSMFormat::DenseInfo::packed_sint64_timestamp, m_timestamps.cbegin(), m_timestamps.cend());
                         pbf_dense_info.add_packed_sint64(OSMFormat::DenseInfo::packed_sint64_changeset, m_changesets.cbegin(), m_changesets.cend());
@@ -389,7 +389,7 @@ namespace osmium {
                     return m_type;
                 }
 
-                size_t size() const {
+                std::size_t size() const {
                     return m_pbf_primitive_group_data.size() + m_stringtable.size() + m_dense_nodes.size();
                 }
 
@@ -399,7 +399,7 @@ namespace osmium {
                  * enough space for the string table (which typically
                  * needs about 0.1 to 0.3% of the block size).
                  */
-                constexpr static size_t max_used_blob_size = max_uncompressed_blob_size * 95 / 100;
+                constexpr static std::size_t max_used_blob_size = max_uncompressed_blob_size * 95 / 100;
 
                 bool can_add(OSMFormat::PrimitiveGroup type) const {
                     if (type != m_type) {
@@ -425,16 +425,16 @@ namespace osmium {
                     }
 
                     std::string primitive_block_data;
-                    protozero::pbf_builder<OSMFormat::PrimitiveBlock> primitive_block(primitive_block_data);
+                    protozero::pbf_builder<OSMFormat::PrimitiveBlock> primitive_block{primitive_block_data};
 
                     {
-                        protozero::pbf_builder<OSMFormat::StringTable> pbf_string_table(primitive_block, OSMFormat::PrimitiveBlock::required_StringTable_stringtable);
+                        protozero::pbf_builder<OSMFormat::StringTable> pbf_string_table{primitive_block, OSMFormat::PrimitiveBlock::required_StringTable_stringtable};
                         m_primitive_block.write_stringtable(pbf_string_table);
                     }
 
                     primitive_block.add_message(OSMFormat::PrimitiveBlock::repeated_PrimitiveGroup_primitivegroup, m_primitive_block.group_data());
 
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(
+                    m_output_queue.push(m_pool.submit(
                         SerializeBlob{std::move(primitive_block_data),
                                       pbf_blob_type::data,
                                       m_options.use_compression}
@@ -458,7 +458,7 @@ namespace osmium {
                     }
 
                     if (m_options.add_metadata) {
-                        protozero::pbf_builder<OSMFormat::Info> pbf_info(pbf_object, T::enum_type::optional_Info_info);
+                        protozero::pbf_builder<OSMFormat::Info> pbf_info{pbf_object, T::enum_type::optional_Info_info};
 
                         pbf_info.add_int32(OSMFormat::Info::optional_int32_version, static_cast_with_assert<int32_t>(object.version()));
                         pbf_info.add_int64(OSMFormat::Info::optional_int64_timestamp, uint32_t(object.timestamp()));
@@ -480,8 +480,8 @@ namespace osmium {
 
             public:
 
-                PBFOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) :
-                    OutputFormat(output_queue),
+                PBFOutputFormat(osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) :
+                    OutputFormat(pool, output_queue),
                     m_options(),
                     m_primitive_block(m_options) {
                     m_options.use_dense_nodes = file.is_not_false("pbf_dense_nodes");
@@ -499,10 +499,10 @@ namespace osmium {
 
                 void write_header(const osmium::io::Header& header) final {
                     std::string data;
-                    protozero::pbf_builder<OSMFormat::HeaderBlock> pbf_header_block(data);
+                    protozero::pbf_builder<OSMFormat::HeaderBlock> pbf_header_block{data};
 
                     if (!header.boxes().empty()) {
-                        protozero::pbf_builder<OSMFormat::HeaderBBox> pbf_header_bbox(pbf_header_block, OSMFormat::HeaderBlock::optional_HeaderBBox_bbox);
+                        protozero::pbf_builder<OSMFormat::HeaderBBox> pbf_header_bbox{pbf_header_block, OSMFormat::HeaderBlock::optional_HeaderBBox_bbox};
 
                         osmium::Box box = header.joined_boxes();
                         pbf_header_bbox.add_sint64(OSMFormat::HeaderBBox::required_sint64_left,   int64_t(box.bottom_left().lon() * lonlat_resolution));
@@ -527,23 +527,23 @@ namespace osmium {
 
                     pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_writingprogram, header.get("generator"));
 
-                    const std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp");
+                    const std::string osmosis_replication_timestamp{header.get("osmosis_replication_timestamp")};
                     if (!osmosis_replication_timestamp.empty()) {
-                        osmium::Timestamp ts(osmosis_replication_timestamp.c_str());
+                        osmium::Timestamp ts{osmosis_replication_timestamp.c_str()};
                         pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp, uint32_t(ts));
                     }
 
-                    const std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number");
+                    const std::string osmosis_replication_sequence_number{header.get("osmosis_replication_sequence_number")};
                     if (!osmosis_replication_sequence_number.empty()) {
                         pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_sequence_number, std::atoll(osmosis_replication_sequence_number.c_str()));
                     }
 
-                    const std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url");
+                    const std::string osmosis_replication_base_url{header.get("osmosis_replication_base_url")};
                     if (!osmosis_replication_base_url.empty()) {
                         pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_osmosis_replication_base_url, osmosis_replication_base_url);
                     }
 
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(
+                    m_output_queue.push(m_pool.submit(
                         SerializeBlob{std::move(data),
                                       pbf_blob_type::header,
                                       m_options.use_compression}
@@ -566,7 +566,7 @@ namespace osmium {
                     }
 
                     switch_primitive_block_type(OSMFormat::PrimitiveGroup::repeated_Node_nodes);
-                    protozero::pbf_builder<OSMFormat::Node> pbf_node{ m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Node_nodes };
+                    protozero::pbf_builder<OSMFormat::Node> pbf_node{m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Node_nodes};
 
                     pbf_node.add_sint64(OSMFormat::Node::required_sint64_id, node.id());
                     add_meta(node, pbf_node);
@@ -577,7 +577,7 @@ namespace osmium {
 
                 void way(const osmium::Way& way) {
                     switch_primitive_block_type(OSMFormat::PrimitiveGroup::repeated_Way_ways);
-                    protozero::pbf_builder<OSMFormat::Way> pbf_way{ m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Way_ways };
+                    protozero::pbf_builder<OSMFormat::Way> pbf_way{m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Way_ways};
 
                     pbf_way.add_int64(OSMFormat::Way::required_int64_id, way.id());
                     add_meta(way, pbf_way);
@@ -610,7 +610,7 @@ namespace osmium {
 
                 void relation(const osmium::Relation& relation) {
                     switch_primitive_block_type(OSMFormat::PrimitiveGroup::repeated_Relation_relations);
-                    protozero::pbf_builder<OSMFormat::Relation> pbf_relation { m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Relation_relations };
+                    protozero::pbf_builder<OSMFormat::Relation> pbf_relation{m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Relation_relations};
 
                     pbf_relation.add_int64(OSMFormat::Relation::required_int64_id, relation.id());
                     add_meta(relation, pbf_relation);
@@ -643,8 +643,8 @@ namespace osmium {
             // we want the register_output_format() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_pbf_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::pbf,
-                [](const osmium::io::File& file, future_string_queue_type& output_queue) {
-                    return new osmium::io::detail::PBFOutputFormat(file, output_queue);
+                [](osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) {
+                    return new osmium::io::detail::PBFOutputFormat{pool, file, output_queue};
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/read_write.hpp b/include/osmium/io/detail/read_write.hpp
index d6aa8bc..388d971 100644
--- a/include/osmium/io/detail/read_write.hpp
+++ b/include/osmium/io/detail/read_write.hpp
@@ -85,7 +85,7 @@ namespace osmium {
 #endif
                 const int fd = ::open(filename.c_str(), flags, 0666);
                 if (fd < 0) {
-                    throw std::system_error(errno, std::system_category(), std::string("Open failed for '") + filename + "'");
+                    throw std::system_error{errno, std::system_category(), std::string("Open failed for '") + filename + "'"};
                 }
                 return fd;
             }
@@ -109,7 +109,7 @@ namespace osmium {
 #endif
                 const int fd = ::open(filename.c_str(), flags);
                 if (fd < 0) {
-                    throw std::system_error(errno, std::system_category(), std::string("Open failed for '") + filename + "'");
+                    throw std::system_error{errno, std::system_category(), std::string("Open failed for '") + filename + "'"};
                 }
                 return fd;
             }
@@ -134,7 +134,7 @@ namespace osmium {
                     }
                     const auto length = ::write(fd, output_buffer + offset, static_cast<unsigned int>(write_count));
                     if (length < 0) {
-                        throw std::system_error(errno, std::system_category(), "Write failed");
+                        throw std::system_error{errno, std::system_category(), "Write failed"};
                     }
                     offset += static_cast<size_t>(length);
                 } while (offset < size);
@@ -160,13 +160,13 @@ namespace osmium {
 #else
                 if (::fsync(fd) != 0) {
 #endif
-                    throw std::system_error(errno, std::system_category(), "Fsync failed");
+                    throw std::system_error{errno, std::system_category(), "Fsync failed"};
                 }
             }
 
             inline void reliable_close(const int fd) {
                 if (::close(fd) != 0) {
-                    throw std::system_error(errno, std::system_category(), "Close failed");
+                    throw std::system_error{errno, std::system_category(), "Close failed"};
                 }
             }
 
diff --git a/include/osmium/io/detail/string_table.hpp b/include/osmium/io/detail/string_table.hpp
index ba725e5..16172c5 100644
--- a/include/osmium/io/detail/string_table.hpp
+++ b/include/osmium/io/detail/string_table.hpp
@@ -136,8 +136,12 @@ namespace osmium {
                     const_iterator& operator++() {
                         assert(m_it != m_last);
                         const auto last_pos = m_it->c_str() + m_it->size();
-                        while (m_pos != last_pos && *m_pos) ++m_pos;
-                        if (m_pos != last_pos) ++m_pos;
+                        while (m_pos != last_pos && *m_pos) {
+                            ++m_pos;
+                        }
+                        if (m_pos != last_pos) {
+                            ++m_pos;
+                        }
                         if (m_pos == last_pos) {
                             ++m_it;
                             if (m_it != m_last) {
@@ -150,7 +154,7 @@ namespace osmium {
                     }
 
                     const_iterator operator++(int) {
-                        const_iterator tmp(*this);
+                        const_iterator tmp{*this};
                         operator++();
                         return tmp;
                     }
@@ -175,11 +179,11 @@ namespace osmium {
                     if (m_chunks.front().empty()) {
                         return end();
                     }
-                    return const_iterator(m_chunks.begin(), m_chunks.end());
+                    return {m_chunks.begin(), m_chunks.end()};
                 }
 
                 const_iterator end() const {
-                    return const_iterator(m_chunks.end(), m_chunks.end());
+                    return {m_chunks.end(), m_chunks.end()};
                 }
 
                 // These functions get you some idea how much memory was
@@ -273,7 +277,7 @@ namespace osmium {
                     m_index[cs] = ++m_size;
 
                     if (m_size > max_entries) {
-                        throw osmium::pbf_error("string table has too many entries");
+                        throw osmium::pbf_error{"string table has too many entries"};
                     }
 
                     return m_size;
diff --git a/include/osmium/io/detail/string_util.hpp b/include/osmium/io/detail/string_util.hpp
index 6414785..b450b7b 100644
--- a/include/osmium/io/detail/string_util.hpp
+++ b/include/osmium/io/detail/string_util.hpp
@@ -134,10 +134,10 @@ namespace osmium {
             // Write out the value with four or more hex digits.
             inline void append_min_4_hex_digits(std::string& out, uint32_t value, const char* const hex_digits) {
                 auto
-                v = value & 0xf0000000; if (v) out += hex_digits[v >> 28];
-                v = value & 0x0f000000; if (v) out += hex_digits[v >> 24];
-                v = value & 0x00f00000; if (v) out += hex_digits[v >> 20];
-                v = value & 0x000f0000; if (v) out += hex_digits[v >> 16];
+                v = value & 0xf0000000; if (v) { out += hex_digits[v >> 28]; }
+                v = value & 0x0f000000; if (v) { out += hex_digits[v >> 24]; }
+                v = value & 0x00f00000; if (v) { out += hex_digits[v >> 20]; }
+                v = value & 0x000f0000; if (v) { out += hex_digits[v >> 16]; }
 
                 out += hex_digits[(value >> 12) & 0xf];
                 out += hex_digits[(value >>  8) & 0xf];
diff --git a/include/osmium/io/detail/write_thread.hpp b/include/osmium/io/detail/write_thread.hpp
index 1b07451..575bc6b 100644
--- a/include/osmium/io/detail/write_thread.hpp
+++ b/include/osmium/io/detail/write_thread.hpp
@@ -83,7 +83,7 @@ namespace osmium {
 
                     try {
                         while (true) {
-                            std::string data = m_queue.pop();
+                            const std::string data{m_queue.pop()};
                             if (at_end_of_data(data)) {
                                 break;
                             }
diff --git a/include/osmium/io/detail/xml_input_format.hpp b/include/osmium/io/detail/xml_input_format.hpp
index a14c7f9..82c74eb 100644
--- a/include/osmium/io/detail/xml_input_format.hpp
+++ b/include/osmium/io/detail/xml_input_format.hpp
@@ -627,7 +627,7 @@ namespace osmium {
 
             public:
 
-                XMLParser(parser_arguments& args) :
+                explicit XMLParser(parser_arguments& args) :
                     Parser(args),
                     m_context(context::root),
                     m_last_context(context::root),
diff --git a/include/osmium/io/detail/xml_output_format.hpp b/include/osmium/io/detail/xml_output_format.hpp
index 130a3c7..a889874 100644
--- a/include/osmium/io/detail/xml_output_format.hpp
+++ b/include/osmium/io/detail/xml_output_format.hpp
@@ -396,7 +396,8 @@ namespace osmium {
                         write_attribute("uid", changeset.uid());
                     }
 
-                    if (changeset.bounds()) {
+                    if (!changeset.bounds().bottom_left().is_undefined() ||
+                        !changeset.bounds().top_right().is_undefined()) {
                         detail::append_lat_lon_attributes(*m_out, "min_lat", "min_lon", changeset.bounds().bottom_left());
                         detail::append_lat_lon_attributes(*m_out, "max_lat", "max_lon", changeset.bounds().top_right());
                     }
@@ -430,8 +431,8 @@ namespace osmium {
 
             public:
 
-                XMLOutputFormat(const osmium::io::File& file, future_string_queue_type& output_queue) :
-                    OutputFormat(output_queue),
+                XMLOutputFormat(osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) :
+                    OutputFormat(pool, output_queue),
                     m_options() {
                     m_options.add_metadata      = file.is_not_false("add_metadata");
                     m_options.use_change_ops    = file.is_true("xml_change_format");
@@ -445,14 +446,14 @@ namespace osmium {
                 ~XMLOutputFormat() noexcept final = default;
 
                 void write_header(const osmium::io::Header& header) final {
-                    std::string out = "<?xml version='1.0' encoding='UTF-8'?>\n";
+                    std::string out{"<?xml version='1.0' encoding='UTF-8'?>\n"};
 
                     if (m_options.use_change_ops) {
                         out += "<osmChange version=\"0.6\" generator=\"";
                     } else {
                         out += "<osm version=\"0.6\"";
 
-                        std::string xml_josm_upload = header.get("xml_josm_upload");
+                        const std::string xml_josm_upload{header.get("xml_josm_upload")};
                         if (xml_josm_upload == "true" || xml_josm_upload == "false") {
                             out += " upload=\"";
                             out += xml_josm_upload;
@@ -474,7 +475,7 @@ namespace osmium {
                 }
 
                 void write_buffer(osmium::memory::Buffer&& buffer) final {
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(XMLOutputBlock{std::move(buffer), m_options}));
+                    m_output_queue.push(m_pool.submit(XMLOutputBlock{std::move(buffer), m_options}));
                 }
 
                 void write_end() final {
@@ -494,8 +495,8 @@ namespace osmium {
             // we want the register_output_format() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_xml_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::xml,
-                [](const osmium::io::File& file, future_string_queue_type& output_queue) {
-                    return new osmium::io::detail::XMLOutputFormat(file, output_queue);
+                [](osmium::thread::Pool& pool, const osmium::io::File& file, future_string_queue_type& output_queue) {
+                    return new osmium::io::detail::XMLOutputFormat(pool, file, output_queue);
             });
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/detail/zlib.hpp b/include/osmium/io/detail/zlib.hpp
index f26fb48..b61bf1d 100644
--- a/include/osmium/io/detail/zlib.hpp
+++ b/include/osmium/io/detail/zlib.hpp
@@ -70,7 +70,7 @@ namespace osmium {
                 );
 
                 if (result != Z_OK) {
-                    throw io_error(std::string("failed to compress data: ") + zError(result));
+                    throw io_error{std::string{"failed to compress data: "} + zError(result)};
                 }
 
                 output.resize(output_size);
@@ -100,7 +100,7 @@ namespace osmium {
                 );
 
                 if (result != Z_OK) {
-                    throw io_error(std::string("failed to uncompress data: ") + zError(result));
+                    throw io_error{std::string{"failed to uncompress data: "} + zError(result)};
                 }
 
                 return protozero::data_view{output.data(), output.size()};
diff --git a/include/osmium/io/file.hpp b/include/osmium/io/file.hpp
index d537044..bf7c874 100644
--- a/include/osmium/io/file.hpp
+++ b/include/osmium/io/file.hpp
@@ -113,7 +113,7 @@ namespace osmium {
                 }
 
                 // if filename is a URL, default to XML format
-                const std::string protocol = m_filename.substr(0, m_filename.find_first_of(':'));
+                const std::string protocol{m_filename.substr(0, m_filename.find_first_of(':'))};
                 if (protocol == "http" || protocol == "https") {
                     m_file_format = file_format::xml;
                 }
@@ -176,7 +176,7 @@ namespace osmium {
                     if (pos == std::string::npos) {
                         set(option, true);
                     } else {
-                        std::string value = option.substr(pos+1);
+                        std::string value{option.substr(pos+1)};
                         option.erase(pos);
                         set(option, value);
                     }
@@ -192,7 +192,9 @@ namespace osmium {
             void detect_format_from_suffix(const std::string& name) {
                 std::vector<std::string> suffixes = detail::split(name, '.');
 
-                if (suffixes.empty()) return;
+                if (suffixes.empty()) {
+                    return;
+                }
 
                 // if the last suffix is one of a known set of compressions,
                 // set that compression
@@ -204,7 +206,9 @@ namespace osmium {
                     suffixes.pop_back();
                 }
 
-                if (suffixes.empty()) return;
+                if (suffixes.empty()) {
+                    return;
+                }
 
                 // if the last suffix is one of a known set of formats,
                 // set that format
@@ -233,17 +237,25 @@ namespace osmium {
                     suffixes.pop_back();
                 }
 
-                if (suffixes.empty()) return;
+                if (suffixes.empty()) {
+                    return;
+                }
 
                 if (suffixes.back() == "osm") {
-                    if (m_file_format == file_format::unknown) m_file_format = file_format::xml;
+                    if (m_file_format == file_format::unknown) {
+                        m_file_format = file_format::xml;
+                    }
                     suffixes.pop_back();
                 } else if (suffixes.back() == "osh") {
-                    if (m_file_format == file_format::unknown) m_file_format = file_format::xml;
+                    if (m_file_format == file_format::unknown) {
+                        m_file_format = file_format::xml;
+                    }
                     m_has_multiple_object_versions = true;
                     suffixes.pop_back();
                 } else if (suffixes.back() == "osc") {
-                    if (m_file_format == file_format::unknown) m_file_format = file_format::xml;
+                    if (m_file_format == file_format::unknown) {
+                        m_file_format = file_format::xml;
+                    }
                     m_has_multiple_object_versions = true;
                     set("xml_change_format", true);
                     suffixes.pop_back();
@@ -258,7 +270,7 @@ namespace osmium {
              */
             const File& check() const {
                 if (m_file_format == file_format::unknown) {
-                    std::string msg = "Could not detect file format";
+                    std::string msg{"Could not detect file format"};
                     if (!m_format_string.empty())  {
                         msg += " from format string '";
                         msg += m_format_string;
@@ -272,7 +284,7 @@ namespace osmium {
                         msg += "'";
                     }
                     msg += ".";
-                    throw io_error(msg);
+                    throw io_error{msg};
                 }
                 return *this;
             }
diff --git a/include/osmium/io/gzip_compression.hpp b/include/osmium/io/gzip_compression.hpp
index 27e18ee..49300d3 100644
--- a/include/osmium/io/gzip_compression.hpp
+++ b/include/osmium/io/gzip_compression.hpp
@@ -84,7 +84,7 @@ namespace osmium {
         namespace detail {
 
             OSMIUM_NORETURN inline void throw_gzip_error(gzFile gzfile, const char* msg, int zlib_error = 0) {
-                std::string error("gzip error: ");
+                std::string error{"gzip error: "};
                 error += msg;
                 error += ": ";
                 int errnum = zlib_error;
@@ -93,7 +93,7 @@ namespace osmium {
                 } else {
                     error += ::gzerror(gzfile, &errnum);
                 }
-                throw osmium::gzip_error(error, errnum);
+                throw osmium::gzip_error{error, errnum};
             }
 
         } // namespace detail
@@ -124,7 +124,7 @@ namespace osmium {
 
             void write(const std::string& data) final {
                 if (!data.empty()) {
-                    int nwrite = ::gzwrite(m_gzfile, data.data(), static_cast_with_assert<unsigned int>(data.size()));
+                    const int nwrite = ::gzwrite(m_gzfile, data.data(), static_cast_with_assert<unsigned int>(data.size()));
                     if (nwrite == 0) {
                         detail::throw_gzip_error(m_gzfile, "write failed");
                     }
@@ -133,7 +133,7 @@ namespace osmium {
 
             void close() final {
                 if (m_gzfile) {
-                    int result = ::gzclose(m_gzfile);
+                    const int result = ::gzclose(m_gzfile);
                     m_gzfile = nullptr;
                     if (result != Z_OK) {
                         detail::throw_gzip_error(m_gzfile, "write close failed", result);
@@ -184,7 +184,7 @@ namespace osmium {
 
             void close() final {
                 if (m_gzfile) {
-                    int result = ::gzclose(m_gzfile);
+                    const int result = ::gzclose(m_gzfile);
                     m_gzfile = nullptr;
                     if (result != Z_OK) {
                         detail::throw_gzip_error(m_gzfile, "read close failed", result);
@@ -208,13 +208,13 @@ namespace osmium {
                 m_zstream() {
                 m_zstream.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(buffer));
                 m_zstream.avail_in = static_cast_with_assert<unsigned int>(size);
-                int result = inflateInit2(&m_zstream, MAX_WBITS | 32);
+                const int result = inflateInit2(&m_zstream, MAX_WBITS | 32);
                 if (result != Z_OK) {
-                    std::string message("gzip error: decompression init failed: ");
+                    std::string message{"gzip error: decompression init failed: "};
                     if (m_zstream.msg) {
                         message.append(m_zstream.msg);
                     }
-                    throw osmium::gzip_error(message, result);
+                    throw osmium::gzip_error{message, result};
                 }
             }
 
@@ -234,7 +234,7 @@ namespace osmium {
                     output.append(buffer_size, '\0');
                     m_zstream.next_out = reinterpret_cast<unsigned char*>(const_cast<char*>(output.data()));
                     m_zstream.avail_out = buffer_size;
-                    int result = inflate(&m_zstream, Z_SYNC_FLUSH);
+                    const int result = inflate(&m_zstream, Z_SYNC_FLUSH);
 
                     if (result != Z_OK) {
                         m_buffer = nullptr;
@@ -246,7 +246,7 @@ namespace osmium {
                         if (m_zstream.msg) {
                             message.append(m_zstream.msg);
                         }
-                        throw osmium::gzip_error(message, result);
+                        throw osmium::gzip_error{message, result};
                     }
 
                     output.resize(static_cast<unsigned long>(m_zstream.next_out - reinterpret_cast<const unsigned char*>(output.data())));
@@ -266,9 +266,9 @@ namespace osmium {
             // we want the register_compression() function to run, setting
             // the variable is only a side-effect, it will never be used
             const bool registered_gzip_compression = osmium::io::CompressionFactory::instance().register_compression(osmium::io::file_compression::gzip,
-                [](int fd, fsync sync) { return new osmium::io::GzipCompressor(fd, sync); },
-                [](int fd) { return new osmium::io::GzipDecompressor(fd); },
-                [](const char* buffer, size_t size) { return new osmium::io::GzipBufferDecompressor(buffer, size); }
+                [](int fd, fsync sync) { return new osmium::io::GzipCompressor{fd, sync}; },
+                [](int fd) { return new osmium::io::GzipDecompressor{fd}; },
+                [](const char* buffer, size_t size) { return new osmium::io::GzipBufferDecompressor{buffer, size}; }
             );
 
             // dummy function to silence the unused variable warning from above
diff --git a/include/osmium/io/header.hpp b/include/osmium/io/header.hpp
index c0a2043..f529294 100644
--- a/include/osmium/io/header.hpp
+++ b/include/osmium/io/header.hpp
@@ -75,14 +75,20 @@ namespace osmium {
              * this stream of objects? This should be true for history files
              * and for change files, but not for normal OSM data files.
              */
-            bool m_has_multiple_object_versions = false;
+            bool m_has_multiple_object_versions;
 
         public:
 
-            Header() = default;
+            Header() :
+                Options(),
+                m_boxes(),
+                m_has_multiple_object_versions(false) {
+            }
 
             explicit Header(const std::initializer_list<osmium::util::Options::value_type>& values) :
-                Options(values) {
+                Options(values),
+                m_boxes(),
+                m_has_multiple_object_versions(false) {
             }
 
             /**
diff --git a/include/osmium/io/reader.hpp b/include/osmium/io/reader.hpp
index 55b13be..7286933 100644
--- a/include/osmium/io/reader.hpp
+++ b/include/osmium/io/reader.hpp
@@ -61,6 +61,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/header.hpp>
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/entity_bits.hpp>
+#include <osmium/thread/pool.hpp>
 #include <osmium/thread/util.hpp>
 #include <osmium/util/config.hpp>
 
@@ -70,13 +71,13 @@ namespace osmium {
 
         namespace detail {
 
-            inline size_t get_input_queue_size() noexcept {
-                const size_t n = osmium::config::get_max_queue_size("INPUT", 20);
+            inline std::size_t get_input_queue_size() noexcept {
+                const std::size_t n = osmium::config::get_max_queue_size("INPUT", 20);
                 return n > 2 ? n : 2;
             }
 
-            inline size_t get_osmdata_queue_size() noexcept {
-                const size_t n = osmium::config::get_max_queue_size("OSMDATA", 20);
+            inline std::size_t get_osmdata_queue_size() noexcept {
+                const std::size_t n = osmium::config::get_max_queue_size("OSMDATA", 20);
                 return n > 2 ? n : 2;
             }
 
@@ -92,6 +93,8 @@ namespace osmium {
 
             osmium::io::File m_file;
 
+            osmium::thread::Pool* m_pool = nullptr;
+
             detail::ParserFactory::create_parser_type m_creator;
 
             enum class status {
@@ -117,11 +120,15 @@ namespace osmium {
 
             osmium::thread::thread_handler m_thread;
 
-            size_t m_file_size;
+            std::size_t m_file_size;
 
             osmium::osm_entity_bits::type m_read_which_entities = osmium::osm_entity_bits::all;
             osmium::io::read_meta m_read_metadata = osmium::io::read_meta::yes;
 
+            void set_option(osmium::thread::Pool& pool) noexcept {
+                m_pool = &pool;
+            }
+
             void set_option(osmium::osm_entity_bits::type value) noexcept {
                 m_read_which_entities = value;
             }
@@ -131,14 +138,16 @@ namespace osmium {
             }
 
             // This function will run in a separate thread.
-            static void parser_thread(const detail::ParserFactory::create_parser_type& creator,
+            static void parser_thread(osmium::thread::Pool& pool,
+                                      const detail::ParserFactory::create_parser_type& creator,
                                       detail::future_string_queue_type& input_queue,
                                       detail::future_buffer_queue_type& osmdata_queue,
                                       std::promise<osmium::io::Header>&& header_promise,
                                       osmium::osm_entity_bits::type read_which_entities,
                                       osmium::io::read_meta read_metadata) {
-                std::promise<osmium::io::Header> promise = std::move(header_promise);
+                std::promise<osmium::io::Header> promise{std::move(header_promise)};
                 osmium::io::detail::parser_arguments args = {
+                    pool,
                     input_queue,
                     osmdata_queue,
                     promise,
@@ -206,16 +215,15 @@ namespace osmium {
              * @throws std::system_error if a system call fails.
              */
             static int open_input_file_or_url(const std::string& filename, int* childpid) {
-                std::string protocol = filename.substr(0, filename.find_first_of(':'));
+                const std::string protocol{filename.substr(0, filename.find_first_of(':'))};
                 if (protocol == "http" || protocol == "https" || protocol == "ftp" || protocol == "file") {
 #ifndef _WIN32
                     return execute("curl", filename, childpid);
 #else
                     throw io_error{"Reading OSM files from the network currently not supported on Windows."};
 #endif
-                } else {
-                    return osmium::io::detail::open_for_reading(filename);
                 }
+                return osmium::io::detail::open_for_reading(filename);
             }
 
         public:
@@ -264,9 +272,13 @@ namespace osmium {
                     (set_option(args), 0)...
                 };
 
+                if (!m_pool) {
+                    m_pool = &thread::Pool::default_instance();
+                }
+
                 std::promise<osmium::io::Header> header_promise;
                 m_header_future = header_promise.get_future();
-                m_thread = osmium::thread::thread_handler{parser_thread, std::ref(m_creator), std::ref(m_input_queue), std::ref(m_osmdata_queue), std::move(header_promise), m_read_which_entities, m_read_metadata};
+                m_thread = osmium::thread::thread_handler{parser_thread, std::ref(*m_pool), std::ref(m_creator), std::ref(m_input_queue), std::ref(m_osmdata_queue), std::move(header_promise), m_read_which_entities, m_read_metadata};
             }
 
             template <typename... TArgs>
@@ -408,7 +420,7 @@ namespace osmium {
              * Get the size of the input file. Returns 0 if the file size
              * is not available (for instance when reading from stdin).
              */
-            size_t file_size() const noexcept {
+            std::size_t file_size() const noexcept {
                 return m_file_size;
             }
 
@@ -426,7 +438,7 @@ namespace osmium {
              * object you are reading. Depending on the file type it might
              * do an expensive system call.
              */
-            size_t offset() const noexcept {
+            std::size_t offset() const noexcept {
                 return m_decompressor->offset();
             }
 
@@ -442,10 +454,10 @@ namespace osmium {
          */
         template <typename... TArgs>
         osmium::memory::Buffer read_file(TArgs&&... args) {
-            osmium::memory::Buffer buffer(1024*1024, osmium::memory::Buffer::auto_grow::yes);
+            osmium::memory::Buffer buffer{1024 * 1024, osmium::memory::Buffer::auto_grow::yes};
 
-            Reader reader(std::forward<TArgs>(args)...);
-            while (osmium::memory::Buffer read_buffer = reader.read()) {
+            Reader reader{std::forward<TArgs>(args)...};
+            while (auto read_buffer = reader.read()) {
                 buffer.add_buffer(read_buffer);
                 buffer.commit();
             }
diff --git a/include/osmium/io/writer.hpp b/include/osmium/io/writer.hpp
index 1397a22..dd90a2e 100644
--- a/include/osmium/io/writer.hpp
+++ b/include/osmium/io/writer.hpp
@@ -53,6 +53,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/io/header.hpp>
 #include <osmium/io/writer_options.hpp>
 #include <osmium/memory/buffer.hpp>
+#include <osmium/thread/pool.hpp>
 #include <osmium/thread/util.hpp>
 #include <osmium/util/config.hpp>
 #include <osmium/version.hpp>
@@ -169,8 +170,13 @@ namespace osmium {
                 osmium::io::Header header;
                 overwrite allow_overwrite = overwrite::no;
                 fsync sync = fsync::no;
+                osmium::thread::Pool* pool = nullptr;
             };
 
+            static void set_option(options_type& options, osmium::thread::Pool& pool) {
+                options.pool = &pool;
+            }
+
             static void set_option(options_type& options, const osmium::io::Header& header) {
                 options.header = header;
             }
@@ -223,7 +229,7 @@ namespace osmium {
             explicit Writer(const osmium::io::File& file, TArgs&&... args) :
                 m_file(file.check()),
                 m_output_queue(detail::get_output_queue_size(), "raw_output"),
-                m_output(osmium::io::detail::OutputFormatFactory::instance().create_output(m_file, m_output_queue)),
+                m_output(nullptr),
                 m_buffer(),
                 m_buffer_size(default_buffer_size),
                 m_write_future(),
@@ -236,6 +242,12 @@ namespace osmium {
                     (set_option(options, args), 0)...
                 };
 
+                if (!options.pool) {
+                    options.pool = &thread::Pool::default_instance();
+                }
+
+                m_output = osmium::io::detail::OutputFormatFactory::instance().create_output(*options.pool, m_file, m_output_queue);
+
                 if (options.header.get("generator") == "") {
                     options.header.set("generator", "libosmium/" LIBOSMIUM_VERSION_STRING);
                 }
diff --git a/include/osmium/memory/buffer.hpp b/include/osmium/memory/buffer.hpp
index 48d8409..deaee42 100644
--- a/include/osmium/memory/buffer.hpp
+++ b/include/osmium/memory/buffer.hpp
@@ -1,6 +1,7 @@
 #ifndef OSMIUM_MEMORY_BUFFER_HPP
 #define OSMIUM_MEMORY_BUFFER_HPP
 
+#include <iostream>
 /*
 
 This file is part of Osmium (http://osmcode.org/libosmium).
@@ -58,7 +59,7 @@ namespace osmium {
     struct buffer_is_full : public std::runtime_error {
 
         buffer_is_full() :
-            std::runtime_error("Osmium buffer is full") {
+            std::runtime_error{"Osmium buffer is full"} {
         }
 
     }; // struct buffer_is_full
@@ -110,18 +111,18 @@ namespace osmium {
 
             std::unique_ptr<unsigned char[]> m_memory;
             unsigned char* m_data;
-            size_t m_capacity;
-            size_t m_written;
-            size_t m_committed;
+            std::size_t m_capacity;
+            std::size_t m_written;
+            std::size_t m_committed;
 #ifndef NDEBUG
-            uint8_t m_builder_count{0};
+            uint8_t m_builder_count = 0;
 #endif
             auto_grow m_auto_grow{auto_grow::no};
             std::function<void(Buffer&)> m_full;
 
-            static size_t calculate_capacity(size_t capacity) noexcept {
+            static std::size_t calculate_capacity(std::size_t capacity) noexcept {
                 // The majority of all Nodes will fit into this size.
-                constexpr static const size_t min_capacity = 64;
+                constexpr static const std::size_t min_capacity = 64;
                 if (capacity < min_capacity) {
                     return min_capacity;
                 }
@@ -156,14 +157,14 @@ namespace osmium {
              * @throws std::invalid_argument if the size isn't a multiple of
              *         the alignment.
              */
-            explicit Buffer(unsigned char* data, size_t size) :
+            explicit Buffer(unsigned char* data, std::size_t size) :
                 m_memory(),
                 m_data(data),
                 m_capacity(size),
                 m_written(size),
                 m_committed(size) {
                 if (size % align_bytes != 0) {
-                    throw std::invalid_argument("buffer size needs to be multiple of alignment");
+                    throw std::invalid_argument{"buffer size needs to be multiple of alignment"};
                 }
             }
 
@@ -176,19 +177,23 @@ namespace osmium {
              * @param committed The size of the initialized data. If this is 0, the buffer startes out empty.
              *
              * @throws std::invalid_argument if the capacity or committed isn't
-             *         a multiple of the alignment.
+             *         a multiple of the alignment or if committed is larger
+             *         than capacity.
              */
-            explicit Buffer(unsigned char* data, size_t capacity, size_t committed) :
+            explicit Buffer(unsigned char* data, std::size_t capacity, std::size_t committed) :
                 m_memory(),
                 m_data(data),
                 m_capacity(capacity),
                 m_written(committed),
                 m_committed(committed) {
                 if (capacity % align_bytes != 0) {
-                    throw std::invalid_argument("buffer capacity needs to be multiple of alignment");
+                    throw std::invalid_argument{"buffer capacity needs to be multiple of alignment"};
                 }
                 if (committed % align_bytes != 0) {
-                    throw std::invalid_argument("buffer parameter 'committed' needs to be multiple of alignment");
+                    throw std::invalid_argument{"buffer parameter 'committed' needs to be multiple of alignment"};
+                }
+                if (committed > capacity) {
+                    throw std::invalid_argument{"buffer parameter 'committed' can not be larger than capacity"};
                 }
             }
 
@@ -204,7 +209,7 @@ namespace osmium {
              * @param auto_grow Should this buffer automatically grow when it
              *        becomes to small?
              */
-            explicit Buffer(size_t capacity, auto_grow auto_grow = auto_grow::yes) :
+            explicit Buffer(std::size_t capacity, auto_grow auto_grow = auto_grow::yes) :
                 m_memory(new unsigned char[calculate_capacity(capacity)]),
                 m_data(m_memory.get()),
                 m_capacity(calculate_capacity(capacity)),
@@ -252,7 +257,7 @@ namespace osmium {
              * Returns the capacity of the buffer, ie how many bytes it can
              * contain. Always returns 0 on invalid buffers.
              */
-            size_t capacity() const noexcept {
+            std::size_t capacity() const noexcept {
                 return m_capacity;
             }
 
@@ -260,7 +265,7 @@ namespace osmium {
              * Returns the number of bytes already filled in this buffer.
              * Always returns 0 on invalid buffers.
              */
-            size_t committed() const noexcept {
+            std::size_t committed() const noexcept {
                 return m_committed;
             }
 
@@ -269,7 +274,7 @@ namespace osmium {
              * are not yet committed.
              * Always returns 0 on invalid buffers.
              */
-            size_t written() const noexcept {
+            std::size_t written() const noexcept {
                 return m_written;
             }
 
@@ -297,20 +302,16 @@ namespace osmium {
              * Callback functionality will be removed in the future. Either
              * detect the buffer_is_full exception or use a buffer with
              * auto_grow::yes. If you want to avoid growing buffers, check
-             * that the used size of the buffer (committed()) is small enough
-             * compared to the capacity (for instance small than 90% of the
-             * capacity) before adding anything to the Buffer. If the buffer
-             * is initialized with auto_grow::yes, it will still grow in the
-             * rare case that a very large object will be added taking more
-             * than the difference between committed() and capacity().
+             * the CallbackBuffer class.
              */
-            OSMIUM_DEPRECATED void set_full_callback(std::function<void(Buffer&)> full) {
+            OSMIUM_DEPRECATED void set_full_callback(const std::function<void(Buffer&)>& full) {
                 assert(m_data && "This must be a valid buffer");
                 m_full = full;
             }
 
             /**
-             * Grow capacity of this buffer to the given size.
+             * Grow capacity of this buffer to the given size (which will be
+             * rounded up to the alignment needed).
              * This works only with internally memory-managed buffers.
              * If the given size is not larger than the current capacity,
              * nothing is done.
@@ -321,19 +322,15 @@ namespace osmium {
              *
              * @throws std::logic_error if the buffer doesn't use internal
              *         memory management.
-             * @throws std::invalid_argument if the size isn't a multiple
-             *         of the alignment.
              * @throws std::bad_alloc if there isn't enough memory available.
              */
-            void grow(size_t size) {
+            void grow(std::size_t size) {
                 assert(m_data && "This must be a valid buffer");
                 if (!m_memory) {
-                    throw std::logic_error("Can't grow Buffer if it doesn't use internal memory management.");
+                    throw std::logic_error{"Can't grow Buffer if it doesn't use internal memory management."};
                 }
+                size = calculate_capacity(size);
                 if (m_capacity < size) {
-                    if (size % align_bytes != 0) {
-                        throw std::invalid_argument("buffer capacity needs to be multiple of alignment");
-                    }
                     std::unique_ptr<unsigned char[]> memory(new unsigned char[size]);
                     std::copy_n(m_memory.get(), m_capacity, memory.get());
                     using std::swap;
@@ -355,12 +352,12 @@ namespace osmium {
              *          used as an offset into the buffer to get to the
              *          object being committed by this call.
              */
-            size_t commit() {
+            std::size_t commit() {
                 assert(m_data && "This must be a valid buffer");
                 assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
                 assert(is_aligned());
 
-                const size_t offset = m_committed;
+                const std::size_t offset = m_committed;
                 m_committed = m_written;
                 return offset;
             }
@@ -386,9 +383,9 @@ namespace osmium {
              *
              * @returns Number of bytes in the buffer before it was cleared.
              */
-            size_t clear() {
+            std::size_t clear() {
                 assert(m_builder_count == 0 && "Make sure there are no Builder objects still in scope");
-                const size_t committed = m_committed;
+                const std::size_t committed = m_committed;
                 m_written = 0;
                 m_committed = 0;
                 return committed;
@@ -405,7 +402,7 @@ namespace osmium {
              *          buffer.
              */
             template <typename T>
-            T& get(const size_t offset) const {
+            T& get(const std::size_t offset) const {
                 assert(m_data && "This must be a valid buffer");
                 return *reinterpret_cast<T*>(&m_data[offset]);
             }
@@ -443,7 +440,7 @@ namespace osmium {
              * @throws osmium::buffer_is_full if the buffer is full there is
              *         no callback defined and the buffer isn't auto-growing.
              */
-            unsigned char* reserve_space(const size_t size) {
+            unsigned char* reserve_space(const std::size_t size) {
                 assert(m_data && "This must be a valid buffer");
                 // try to flush the buffer empty first.
                 if (m_written + size > m_capacity && m_full) {
@@ -453,13 +450,13 @@ namespace osmium {
                 if (m_written + size > m_capacity) {
                     if (m_memory && (m_auto_grow == auto_grow::yes)) {
                         // double buffer size until there is enough space
-                        size_t new_capacity = m_capacity * 2;
+                        std::size_t new_capacity = m_capacity * 2;
                         while (m_written + size > new_capacity) {
                             new_capacity *= 2;
                         }
                         grow(new_capacity);
                     } else {
-                        throw osmium::buffer_is_full();
+                        throw osmium::buffer_is_full{};
                     }
                 }
                 unsigned char* data = &m_data[m_written];
@@ -598,7 +595,7 @@ namespace osmium {
              *          in the buffer.
              */
             template <typename T>
-            t_iterator<T> get_iterator(size_t offset) {
+            t_iterator<T> get_iterator(std::size_t offset) {
                 assert(m_data && "This must be a valid buffer");
                 return t_iterator<T>(m_data + offset, m_data + m_committed);
             }
@@ -612,7 +609,7 @@ namespace osmium {
              * @returns Iterator to first OSMEntity after given offset in the
              *          buffer.
              */
-            iterator get_iterator(size_t offset) {
+            iterator get_iterator(std::size_t offset) {
                 assert(m_data && "This must be a valid buffer");
                 return iterator(m_data + offset, m_data + m_committed);
             }
@@ -656,12 +653,12 @@ namespace osmium {
             }
 
             template <typename T>
-            t_const_iterator<T> get_iterator(size_t offset) const {
+            t_const_iterator<T> get_iterator(std::size_t offset) const {
                 assert(m_data && "This must be a valid buffer");
                 return t_const_iterator<T>(m_data + offset, m_data + m_committed);
             }
 
-            const_iterator get_iterator(size_t offset) const {
+            const_iterator get_iterator(std::size_t offset) const {
                 assert(m_data && "This must be a valid buffer");
                 return const_iterator(m_data + offset, m_data + m_committed);
             }
@@ -746,8 +743,8 @@ namespace osmium {
                         if (it_read != it_write) {
                             assert(it_read.data() >= data());
                             assert(it_write.data() >= data());
-                            size_t old_offset = static_cast<size_t>(it_read.data() - data());
-                            size_t new_offset = static_cast<size_t>(it_write.data() - data());
+                            const auto old_offset = static_cast<std::size_t>(it_read.data() - data());
+                            const auto new_offset = static_cast<std::size_t>(it_write.data() - data());
                             callback->moving_in_buffer(old_offset, new_offset);
                             std::memmove(it_write.data(), it_read.data(), it_read->padded_size());
                         }
@@ -756,7 +753,7 @@ namespace osmium {
                 }
 
                 assert(it_write.data() >= data());
-                m_written = static_cast<size_t>(it_write.data() - data());
+                m_written = static_cast<std::size_t>(it_write.data() - data());
                 m_committed = m_written;
             }
 
diff --git a/include/osmium/memory/callback_buffer.hpp b/include/osmium/memory/callback_buffer.hpp
new file mode 100644
index 0000000..7e9be73
--- /dev/null
+++ b/include/osmium/memory/callback_buffer.hpp
@@ -0,0 +1,189 @@
+#ifndef OSMIUM_MEMORY_CALLBACK_BUFFER_HPP
+#define OSMIUM_MEMORY_CALLBACK_BUFFER_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cstddef>
+#include <functional>
+#include <utility>
+
+#include <osmium/memory/buffer.hpp>
+
+namespace osmium {
+
+    namespace memory {
+
+        /**
+         * This is basically a wrapper around osmium::memory::Buffer with an
+         * additional callback function that is called whenever the buffer is
+         * full.
+         *
+         * The internal buffer is created with the `initial_buffer_size` set
+         * in the constructor. When it grows beyond the `max_buffer_size` set
+         * in the constructor, the callback function is called with the buffer
+         * and a new, empty buffer is created internally.
+         *
+         * Note that the buffer can grow beyond the initial buffer size if
+         * needed. This can happen if a new object doesn't fit into the rest
+         * of the buffer available or if no callback function is set (yet).
+         *
+         * Example:
+         * @code
+         *     CallbackBuffer cb;
+         *     cb.set_callback([&](osmium::memory::Buffer&& buffer) {
+         *         ...handle buffer...
+         *     }
+         *     osmium::builder::add_node(cb.buffer(), _id(9), ...);
+         *     osmium::builder::add_way(cb.buffer(), _id(27), ...);
+         * @endcode
+         */
+        class CallbackBuffer {
+
+        public:
+
+            /// The type for the callback function
+            using callback_func_type = std::function<void(osmium::memory::Buffer&&)>;
+
+        private:
+
+            static constexpr const std::size_t default_initial_buffer_size = 1024 * 1024;
+            static constexpr const std::size_t default_max_buffer_size     =  800 * 1024;
+
+            osmium::memory::Buffer m_buffer;
+            std::size_t m_initial_buffer_size;
+            std::size_t m_max_buffer_size;
+            callback_func_type m_callback;
+
+        public:
+
+            /**
+             * Construct a CallbackBuffer without a callback function. You
+             * can later call set the callback with set_callback().
+             *
+             * @param initial_buffer_size The initial size of newly created
+             *                            internal buffers.
+             * @param max_buffer_size If the buffer grows beyond this size the
+             *                        callback will be called.
+             */
+            explicit CallbackBuffer(std::size_t initial_buffer_size = default_initial_buffer_size, std::size_t max_buffer_size = default_max_buffer_size) :
+                m_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes),
+                m_initial_buffer_size(initial_buffer_size),
+                m_max_buffer_size(max_buffer_size),
+                m_callback(nullptr) {
+            }
+
+            /**
+             * Construct a CallbackBuffer with a callback function.
+             *
+             * @param callback The callback function. Must be of type
+             *                 @code void(osmium::memory::Buffer&&) @endcode
+             * @param initial_buffer_size The initial size of newly created
+             *                            internal buffers.
+             * @param max_buffer_size If the buffer grows beyond this size the
+             *                        callback will be called.
+             */
+            explicit CallbackBuffer(const callback_func_type& callback, std::size_t initial_buffer_size = default_initial_buffer_size, std::size_t max_buffer_size = default_max_buffer_size) :
+                m_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes),
+                m_initial_buffer_size(initial_buffer_size),
+                m_max_buffer_size(max_buffer_size),
+                m_callback(callback) {
+            }
+
+            /**
+             * Access the internal buffer. This is used to fill the buffer,
+             * the CallbackBuffer still owns the buffer.
+             *
+             * Use read() or the callback if you need to own the buffer.
+             */
+            osmium::memory::Buffer& buffer() noexcept {
+                return m_buffer;
+            }
+
+            /**
+             * Set the callback. The function must take a rvalue reference to
+             * a buffer and return void.
+             *
+             * @param callback The callback function. Must be of type
+             *                 @code void(osmium::memory::Buffer&&) @endcode
+             */
+            void set_callback(const callback_func_type& callback = nullptr) noexcept {
+                m_callback = callback;
+            }
+
+            /**
+             * Flush the internal buffer regardless of how full it is. Calls
+             * the callback with the buffer and creates an new empty internal
+             * one.
+             *
+             * This will do nothing if no callback is set or if the buffer
+             * is empty.
+             */
+            void flush() {
+                if (m_callback && m_buffer.committed() > 0) {
+                    m_callback(read());
+                }
+            }
+
+            /**
+             * Flush the internal buffer if and only if it contains more than
+             * the max_buffer_size set in the constructor. Calls the callback
+             * with the buffer and creates an new empty internal one.
+             *
+             * This will do nothing if no callback is set or if the buffer
+             * is empty.
+             */
+            void possibly_flush() {
+                if (m_buffer.committed() > m_max_buffer_size) {
+                    flush();
+                }
+            }
+
+            /**
+             * Return the internal buffer and create a new empty internal one.
+             * You can use this as an alternative access instead of using the
+             * callback.
+             */
+            osmium::memory::Buffer read() {
+                osmium::memory::Buffer buffer{m_initial_buffer_size, osmium::memory::Buffer::auto_grow::yes};
+                using std::swap;
+                swap(buffer, m_buffer);
+                return buffer;
+            }
+
+        }; // class CallbackBuffer
+
+    } // namespace memory
+
+} // namespace osmium
+
+#endif // OSMIUM_MEMORY_CALLBACK_BUFFER_HPP
diff --git a/include/osmium/memory/item.hpp b/include/osmium/memory/item.hpp
index 6714ce2..0f02df9 100644
--- a/include/osmium/memory/item.hpp
+++ b/include/osmium/memory/item.hpp
@@ -59,7 +59,7 @@ namespace osmium {
         using item_size_type = uint32_t;
 
         // align datastructures to this many bytes
-        constexpr const item_size_type align_bytes = 8;
+        constexpr const std::size_t align_bytes = 8;
 
         inline constexpr std::size_t padded_length(std::size_t length) noexcept {
             return (length + align_bytes - 1) & ~(align_bytes - 1);
diff --git a/include/osmium/object_pointer_collection.hpp b/include/osmium/object_pointer_collection.hpp
index bc94ec6..685211d 100644
--- a/include/osmium/object_pointer_collection.hpp
+++ b/include/osmium/object_pointer_collection.hpp
@@ -116,7 +116,7 @@ namespace osmium {
          *
          * Complexity: Constant.
          */
-        size_t size() const noexcept {
+        std::size_t size() const noexcept {
             return m_objects.size();
         }
 
@@ -126,19 +126,19 @@ namespace osmium {
         }
 
         iterator begin() {
-            return iterator{m_objects.begin()};
+            return {m_objects.begin()};
         }
 
         iterator end() {
-            return iterator{m_objects.end()};
+            return {m_objects.end()};
         }
 
         const_iterator cbegin() const {
-            return const_iterator{m_objects.cbegin()};
+            return {m_objects.cbegin()};
         }
 
         const_iterator cend() const {
-            return const_iterator{m_objects.cend()};
+            return {m_objects.cend()};
         }
 
     }; // class ObjectPointerCollection
diff --git a/include/osmium/opl.hpp b/include/osmium/opl.hpp
index c77a6c0..9969914 100644
--- a/include/osmium/opl.hpp
+++ b/include/osmium/opl.hpp
@@ -33,8 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <osmium/memory/buffer.hpp>
 #include <osmium/io/detail/opl_parser_functions.hpp>
+#include <osmium/memory/buffer.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/osm/area.hpp b/include/osmium/osm/area.hpp
index a980552..12a73be 100644
--- a/include/osmium/osm/area.hpp
+++ b/include/osmium/osm/area.hpp
@@ -171,8 +171,8 @@ namespace osmium {
         std::pair<size_t, size_t> num_rings() const {
             std::pair<size_t, size_t> counter{0, 0};
 
-            for (auto it = cbegin(); it != cend(); ++it) {
-                switch (it->type()) {
+            for (const auto& item : *this) {
+                switch (item.type()) {
                     case osmium::item_type::outer_ring:
                         ++counter.first;
                         break;
diff --git a/include/osmium/osm/changeset.hpp b/include/osmium/osm/changeset.hpp
index 7fbd466..d79ec31 100644
--- a/include/osmium/osm/changeset.hpp
+++ b/include/osmium/osm/changeset.hpp
@@ -61,9 +61,9 @@ namespace osmium {
         friend class osmium::builder::ChangesetDiscussionBuilder;
 
         osmium::Timestamp m_date;
-        osmium::user_id_type m_uid {0};
+        osmium::user_id_type m_uid = 0;
+        changeset_comment_size_type m_text_size;
         string_size_type m_user_size;
-        string_size_type m_text_size;
 
         ChangesetComment(const ChangesetComment&) = delete;
         ChangesetComment(ChangesetComment&&) = delete;
@@ -94,7 +94,7 @@ namespace osmium {
             m_user_size = size;
         }
 
-        void set_text_size(string_size_type size) noexcept {
+        void set_text_size(changeset_comment_size_type size) noexcept {
             m_text_size = size;
         }
 
@@ -105,8 +105,8 @@ namespace osmium {
         ChangesetComment(osmium::Timestamp date, osmium::user_id_type uid) noexcept :
             m_date(date),
             m_uid(uid),
-            m_user_size(0),
-            m_text_size(0) {
+            m_text_size(0),
+            m_user_size(0) {
         }
 
         osmium::Timestamp date() const noexcept {
@@ -153,13 +153,13 @@ namespace osmium {
         osmium::Box       m_bounds;
         osmium::Timestamp m_created_at;
         osmium::Timestamp m_closed_at;
-        changeset_id_type m_id {0};
-        num_changes_type  m_num_changes {0};
-        num_comments_type m_num_comments {0};
-        user_id_type      m_uid {0};
-        string_size_type  m_user_size;
-        int16_t           m_padding1 {0};
-        int32_t           m_padding2 {0};
+        changeset_id_type m_id = 0;
+        num_changes_type  m_num_changes = 0;
+        num_comments_type m_num_comments = 0;
+        user_id_type      m_uid = 0;
+        string_size_type  m_user_size = 0;
+        int16_t           m_padding1 = 0;
+        int32_t           m_padding2 = 0;
 
         Changeset() :
             OSMEntity(sizeof(Changeset), osmium::item_type::changeset) {
diff --git a/include/osmium/osm/location.hpp b/include/osmium/osm/location.hpp
index b2fdc1b..f5ae9e5 100644
--- a/include/osmium/osm/location.hpp
+++ b/include/osmium/osm/location.hpp
@@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <algorithm>
 #include <cmath>
 #include <cstdint>
 #include <cstring>
@@ -198,6 +199,12 @@ namespace osmium {
         // Convert integer as used by location for coordinates into a string.
         template <typename T>
         inline T append_location_coordinate_to_string(T iterator, int32_t value) {
+            // need to special-case this, because later `value = -value` would overflow.
+            if (value == std::numeric_limits<int32_t>::min()) {
+                static const char minresult[] = "-214.7483648";
+                return std::copy_n(minresult, sizeof(minresult) - 1, iterator);
+            }
+
             // handle negative values
             if (value < 0) {
                 *iterator++ = '-';
@@ -329,6 +336,9 @@ namespace osmium {
         /**
          * Check whether the coordinates of this location
          * are defined.
+         *
+         * @deprecated Use is_defined() or is_undefined() or is_valid() which
+         *             have all slightly different meanings.
          */
         explicit constexpr operator bool() const noexcept {
             return m_x != undefined_coordinate && m_y != undefined_coordinate;
@@ -337,6 +347,8 @@ namespace osmium {
         /**
          * Check whether the coordinates are inside the
          * usual bounds (-180<=lon<=180, -90<=lat<=90).
+         *
+         * See also is_defined() and is_undefined().
          */
         constexpr bool valid() const noexcept {
             return m_x >= -180 * detail::coordinate_precision
@@ -345,6 +357,24 @@ namespace osmium {
                 && m_y <=   90 * detail::coordinate_precision;
         }
 
+        /**
+         * Returns true if at least one of the coordinates is defined.
+         *
+         * See also is_undefined() and is_valid().
+         */
+        constexpr bool is_defined() const noexcept {
+            return m_x != undefined_coordinate || m_y != undefined_coordinate;
+        }
+
+        /**
+         * Returns true if both coordinates are undefined.
+         *
+         * See also is_defined() and is_valid().
+         */
+        constexpr bool is_undefined() const noexcept {
+            return m_x == undefined_coordinate && m_y == undefined_coordinate;
+        }
+
         constexpr int32_t x() const noexcept {
             return m_x;
         }
@@ -370,7 +400,7 @@ namespace osmium {
          */
         double lon() const {
             if (!valid()) {
-                throw osmium::invalid_location("invalid location");
+                throw osmium::invalid_location{"invalid location"};
             }
             return fix_to_double(m_x);
         }
@@ -389,7 +419,7 @@ namespace osmium {
          */
         double lat() const {
             if (!valid()) {
-                throw osmium::invalid_location("invalid location");
+                throw osmium::invalid_location{"invalid location"};
             }
             return fix_to_double(m_y);
         }
@@ -449,7 +479,7 @@ namespace osmium {
         template <typename T>
         T as_string(T iterator, const char separator = ',') const {
             if (!valid()) {
-                throw osmium::invalid_location("invalid location");
+                throw osmium::invalid_location{"invalid location"};
             }
             return as_string_without_check(iterator, separator);
         }
diff --git a/include/osmium/osm/object.hpp b/include/osmium/osm/object.hpp
index 66f891a..1e477db 100644
--- a/include/osmium/osm/object.hpp
+++ b/include/osmium/osm/object.hpp
@@ -193,7 +193,7 @@ namespace osmium {
             } else if (!std::strcmp("false", visible)) {
                 set_visible(false);
             } else {
-                throw std::invalid_argument("Unknown value for visible attribute (allowed is 'true' or 'false')");
+                throw std::invalid_argument{"Unknown value for visible attribute (allowed is 'true' or 'false')"};
             }
             return *this;
         }
@@ -468,18 +468,19 @@ namespace osmium {
      * ordering by timestamp is not necessary as there shouldn't be two
      * objects with the same type, id, and version. But this can happen when
      * creating diff files from extracts, so we take the timestamp into
-     * account  here.
+     * account here.
      *
      * Note that we use the absolute value of the id for a better ordering
-     * of objects with negative id. If the IDs have the same absolute value,
-     * the positive ID comes first.
+     * of objects with negative id. All the negative IDs come first, then the
+     * positive IDs. IDs are ordered by their absolute values. (This is the
+     * same ordering JOSM uses.)
      *
      * See object_order_type_id_reverse_version if you need a different
      * ordering.
      */
     inline bool operator<(const OSMObject& lhs, const OSMObject& rhs) noexcept {
-        return const_tie(lhs.type(), lhs.positive_id(), lhs.id() < 0, lhs.version(), lhs.timestamp()) <
-               const_tie(rhs.type(), rhs.positive_id(), rhs.id() < 0, rhs.version(), rhs.timestamp());
+        return const_tie(lhs.type(), lhs.id() > 0, lhs.positive_id(), lhs.version(), lhs.timestamp()) <
+               const_tie(rhs.type(), rhs.id() > 0, rhs.positive_id(), rhs.version(), rhs.timestamp());
     }
 
     inline bool operator>(const OSMObject& lhs, const OSMObject& rhs) noexcept {
diff --git a/include/osmium/osm/object_comparisons.hpp b/include/osmium/osm/object_comparisons.hpp
index 69dbf79..a762f19 100644
--- a/include/osmium/osm/object_comparisons.hpp
+++ b/include/osmium/osm/object_comparisons.hpp
@@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cassert>
+#include <cstdlib>
 #include <tuple>
 
 #include <osmium/osm/object.hpp>
@@ -44,7 +45,7 @@ namespace osmium {
 
     /**
      * Function object class for comparing OSM objects for equality by type,
-     * id, and version.
+     * ID, and version.
      */
     struct object_equal_type_id_version {
 
@@ -62,7 +63,7 @@ namespace osmium {
 
     /**
      * Function object class for comparing OSM objects for equality by type
-     * and id, ignoring the version.
+     * and ID, ignoring the version.
      */
     struct object_equal_type_id {
 
@@ -80,6 +81,19 @@ namespace osmium {
     }; // struct object_equal_type_id
 
     /**
+     * Compare two objects IDs. Order is as follows: 0 first, then negative
+     * IDs, then positive IDs, both ordered by their absolute values.
+     */
+    struct id_order {
+
+        bool operator()(const object_id_type lhs, const object_id_type rhs) const noexcept {
+            return const_tie(lhs > 0, std::abs(lhs)) <
+                   const_tie(rhs > 0, std::abs(rhs));
+        }
+
+    }; // struct id_order
+
+    /**
      * Function object class for ordering OSM objects by type, id, version,
      * and timestamp.
      */
@@ -98,17 +112,18 @@ namespace osmium {
     }; // struct object_order_type_id_version
 
     /**
-     * Function object class for ordering OSM objects by type, id, and
-     * reverse version, timestamp. So objects are ordered by type and id, but
-     * later versions of an object are ordered before earlier versions of the
-     * same object. This is useful when the last version of an object needs
-     * to be used.
+     * Function object class for ordering OSM objects by type, ID, and
+     * reverse version, timestamp. So objects are ordered by type and ID
+     * (negative IDs first, then positive IDs, both in the order of their
+     * absolute values), but later versions of an object are ordered before
+     * earlier versions of the same object. This is useful when the last
+     * version of an object needs to be used.
      */
     struct object_order_type_id_reverse_version {
 
         bool operator()(const osmium::OSMObject& lhs, const osmium::OSMObject& rhs) const noexcept {
-            return const_tie(lhs.type(), lhs.id() < 0, lhs.positive_id(), rhs.version(), rhs.timestamp()) <
-                   const_tie(rhs.type(), rhs.id() < 0, rhs.positive_id(), lhs.version(), lhs.timestamp());
+            return const_tie(lhs.type(), lhs.id() > 0, lhs.positive_id(), rhs.version(), rhs.timestamp()) <
+                   const_tie(rhs.type(), rhs.id() > 0, rhs.positive_id(), lhs.version(), lhs.timestamp());
         }
 
         /// @pre lhs and rhs must not be nullptr
diff --git a/include/osmium/osm/relation.hpp b/include/osmium/osm/relation.hpp
index deac43a..8122a1f 100644
--- a/include/osmium/osm/relation.hpp
+++ b/include/osmium/osm/relation.hpp
@@ -61,7 +61,7 @@ namespace osmium {
         object_id_type   m_ref;
         item_type        m_type;
         uint16_t         m_flags;
-        string_size_type m_role_size {0};
+        string_size_type m_role_size = 0;
 
         RelationMember(const RelationMember&) = delete;
         RelationMember(RelationMember&&) = delete;
@@ -183,14 +183,21 @@ namespace osmium {
             return t == itemtype;
         }
 
+        /// Get a reference to the member list.
         RelationMemberList& members() {
             return osmium::detail::subitem_of_type<RelationMemberList>(begin(), end());
         }
 
+        /// Get a const reference to the member list.
         const RelationMemberList& members() const {
             return osmium::detail::subitem_of_type<const RelationMemberList>(cbegin(), cend());
         }
 
+        /// Get a const reference to the member list.
+        const RelationMemberList& cmembers() const {
+            return osmium::detail::subitem_of_type<const RelationMemberList>(cbegin(), cend());
+        }
+
     }; // class Relation
 
     static_assert(sizeof(Relation) % osmium::memory::align_bytes == 0, "Class osmium::Relation has wrong size to be aligned properly!");
diff --git a/include/osmium/osm/types.hpp b/include/osmium/osm/types.hpp
index 6a801c1..8c7e48a 100644
--- a/include/osmium/osm/types.hpp
+++ b/include/osmium/osm/types.hpp
@@ -58,6 +58,13 @@ namespace osmium {
      */
     using string_size_type = uint16_t;
 
+    /**
+     * This is the size type for the text in a changeset discussion comment.
+     * There is no official limit for this. 16 bit is not enough for existing
+     * OSM changesets, hopefully 32 bit is.
+     */
+    using changeset_comment_size_type = uint32_t;
+
     // maximum of 256 characters of max 4 bytes each (in UTF-8 encoding)
     constexpr const int max_osm_string_length = 256 * 4;
 
diff --git a/include/osmium/osm/types_from_string.hpp b/include/osmium/osm/types_from_string.hpp
index d2a1421..43f6203 100644
--- a/include/osmium/osm/types_from_string.hpp
+++ b/include/osmium/osm/types_from_string.hpp
@@ -61,12 +61,14 @@ namespace osmium {
         assert(input);
         if (*input != '\0' && !std::isspace(*input)) {
             char* end;
-            auto id = std::strtoll(input, &end, 10);
-            if (id != std::numeric_limits<long long>::min() && id != std::numeric_limits<long long>::max() && *end == '\0') {
+            const auto id = std::strtoll(input, &end, 10);
+            if (id != std::numeric_limits<long long>::min() &&
+                id != std::numeric_limits<long long>::max() &&
+                *end == '\0') {
                 return id;
             }
         }
-        throw std::range_error(std::string("illegal id: '") + input + "'");
+        throw std::range_error{std::string{"illegal id: '"} + input + "'"};
     }
 
     /**
@@ -95,12 +97,12 @@ namespace osmium {
             if (std::isdigit(*input)) {
                 return std::make_pair(default_type, string_to_object_id(input));
             }
-            osmium::item_type t = osmium::char_to_item_type(*input);
+            const osmium::item_type t = osmium::char_to_item_type(*input);
             if (osmium::osm_entity_bits::from_item_type(t) & types) {
                 return std::make_pair(t, string_to_object_id(input + 1));
             }
         }
-        throw std::range_error(std::string("not a valid id: '") + input + "'");
+        throw std::range_error{std::string{"not a valid id: '"} + input + "'"};
     }
 
     namespace detail {
@@ -108,12 +110,12 @@ namespace osmium {
         inline unsigned long string_to_ulong(const char* input, const char* name) {
             if (*input != '\0' && *input != '-' && !std::isspace(*input)) {
                 char* end;
-                auto value = std::strtoul(input, &end, 10);
+                const auto value = std::strtoul(input, &end, 10);
                 if (value != std::numeric_limits<unsigned long>::max() && *end == '\0') {
                     return value;
                 }
             }
-            throw std::range_error(std::string("illegal ") + name + ": '" + input + "'");
+            throw std::range_error{std::string{"illegal "} + name + ": '" + input + "'"};
         }
 
     } // namespace detail
diff --git a/include/osmium/relations/collector.hpp b/include/osmium/relations/collector.hpp
index dfb6da8..ccfed99 100644
--- a/include/osmium/relations/collector.hpp
+++ b/include/osmium/relations/collector.hpp
@@ -250,7 +250,7 @@ namespace osmium {
              * this is for instance used to only keep members of type way and
              * ignore all others.
              */
-            bool keep_member(const osmium::relations::RelationMeta& /*relation_meta*/, const osmium::RelationMember& /*member*/) const {
+            bool keep_member(const RelationMeta& /*relation_meta*/, const osmium::RelationMember& /*member*/) const {
                 return true;
             }
 
@@ -419,7 +419,7 @@ namespace osmium {
                 return true;
             }
 
-            void clear_member_metas(const osmium::relations::RelationMeta& relation_meta) {
+            void clear_member_metas(const RelationMeta& relation_meta) {
                 const osmium::Relation& relation = get_relation(relation_meta);
                 for (const auto& member : relation.members()) {
                     if (member.ref() != 0) {
@@ -539,9 +539,8 @@ namespace osmium {
                 assert(!range.empty());
                 if (range.begin()->is_available()) {
                     return std::make_pair(true, range.begin()->buffer_offset());
-                } else {
-                    return std::make_pair(false, 0);
                 }
+                return std::make_pair(false, 0);
             }
 
             template <typename TIter>
diff --git a/include/osmium/relations/manager_util.hpp b/include/osmium/relations/manager_util.hpp
new file mode 100644
index 0000000..1ee8dd4
--- /dev/null
+++ b/include/osmium/relations/manager_util.hpp
@@ -0,0 +1,197 @@
+#ifndef OSMIUM_RELATIONS_MANAGER_UTIL_HPP
+#define OSMIUM_RELATIONS_MANAGER_UTIL_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cstddef>
+#include <initializer_list>
+#include <iomanip>
+#include <utility>
+
+#include <osmium/fwd.hpp>
+#include <osmium/handler.hpp>
+#include <osmium/handler/check_order.hpp>
+#include <osmium/io/file.hpp>
+#include <osmium/io/reader.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/util/progress_bar.hpp>
+#include <osmium/visitor.hpp>
+
+namespace osmium {
+
+    namespace relations {
+
+        /**
+         * This is a handler class used for the second pass of relation
+         * managers. An object of this class is instantiated as a member
+         * of the Manager and used to re-direct all calls to the handler
+         * to the "parent" manager.
+         *
+         * @tparam TManager The manager we want to call functions on.
+         */
+        template <typename TManager>
+        class SecondPassHandler : public osmium::handler::Handler {
+
+            TManager& m_manager;
+
+        public:
+
+            explicit SecondPassHandler(TManager& manager) noexcept :
+                m_manager(manager) {
+            }
+
+            /**
+             * Overwrites the function in the handler parent class.
+             */
+            void node(const osmium::Node& node) {
+                m_manager.handle_node(node);
+            }
+
+            /**
+             * Overwrites the function in the handler parent class.
+             */
+            void way(const osmium::Way& way) {
+                m_manager.handle_way(way);
+            }
+
+            /**
+             * Overwrites the function in the handler parent class.
+             */
+            void relation(const osmium::Relation& relation) {
+                m_manager.handle_relation(relation);
+            }
+
+            /**
+             * Overwrites the function in the handler parent class.
+             *
+             * Calls the flush_output() function on the manager.
+             */
+            void flush() {
+                m_manager.flush_output();
+            }
+
+        }; // class SecondPassHandler
+
+        /**
+         * Read relations from file and feed them into all the managers
+         * specified as parameters. Opens an osmium::io::Reader internally
+         * with the file parameter.
+         *
+         * After the file is read, the prepare_for_lookup() function is called
+         * on all the managers making them ready for querying the data they
+         * have stored.
+         *
+         * @tparam TManager Any number of relation manager types.
+         * @param file The file that should be opened with an osmium::io::Reader.
+         * @param managers Relation managers we want the relations to be sent
+         *                 to.
+         */
+        template <typename ...TManager>
+        void read_relations(const osmium::io::File& file, TManager&& ...managers) {
+            static_assert(sizeof...(TManager) > 0, "Need at least one manager as parameter.");
+            osmium::io::Reader reader{file, osmium::osm_entity_bits::relation};
+            osmium::apply(reader, std::forward<TManager>(managers)...);
+            reader.close();
+            (void)std::initializer_list<int>{
+                (std::forward<TManager>(managers).prepare_for_lookup(), 0)...
+            };
+        }
+
+        /**
+         * Read relations from file and feed them into all the managers
+         * specified as parameters. Opens an osmium::io::Reader internally
+         * with the file parameter.
+         *
+         * After the file is read, the prepare_for_lookup() function is called
+         * on all the managers making them ready for querying the data they
+         * have stored.
+         *
+         * @tparam TManager Any number of relation manager types.
+         * @param progress_bar Reference to osmium::ProgressBar object that
+         *                     will be updated while reading the data.
+         * @param file The file that should be opened with an osmium::io::Reader.
+         * @param managers Relation managers we want the relations to be sent
+         *                 to.
+         */
+        template <typename ...TManager>
+        void read_relations(osmium::ProgressBar& progress_bar, const osmium::io::File& file, TManager&& ...managers) {
+            static_assert(sizeof...(TManager) > 0, "Need at least one manager as parameter.");
+            osmium::io::Reader reader{file, osmium::osm_entity_bits::relation};
+            while (auto buffer = reader.read()) {
+                progress_bar.update(reader.offset());
+                osmium::apply(buffer, std::forward<TManager>(managers)...);
+            }
+            reader.close();
+            (void)std::initializer_list<int>{
+                (std::forward<TManager>(managers).prepare_for_lookup(), 0)...
+            };
+            progress_bar.file_done(file.size());
+        }
+
+        /**
+         * Struct for memory usage numbers returned by various relations
+         * managers from the used_memory() function.
+         */
+        struct relations_manager_memory_usage {
+            std::size_t relations_db;
+            std::size_t members_db;
+            std::size_t stash;
+        };
+
+        /**
+         * Prints relations managers memory usage numbers to the specified
+         * stream.
+         *
+         * @tparam TStream Output stream type (like std::cout, std::cerr, or
+         *                 osmium::util::VerboseOutput).
+         * @param stream Reference to stream where the output should go.
+         * @param mu Memory usage data as returned by the used_memory()
+         *                  functions of various relations managers.
+         */
+        template <typename TStream>
+        void print_used_memory(TStream& stream, const relations_manager_memory_usage& mu) {
+            const auto total = mu.relations_db + mu.members_db + mu.stash;
+
+            stream << "  relations: " << std::setw(8) << (mu.relations_db / 1024) << " kB\n"
+                   << "  members:   " << std::setw(8) << (mu.members_db   / 1024) << " kB\n"
+                   << "  stash:     " << std::setw(8) << (mu.stash        / 1024) << " kB\n"
+                   << "  total:     " << std::setw(8) << (total           / 1024) << " kB\n"
+                   << "  ======================\n";
+        }
+
+    } // namespace relations
+
+} // namespace osmium
+
+#endif // OSMIUM_RELATIONS_MANAGER_UTIL_HPP
diff --git a/include/osmium/relations/members_database.hpp b/include/osmium/relations/members_database.hpp
new file mode 100644
index 0000000..975f003
--- /dev/null
+++ b/include/osmium/relations/members_database.hpp
@@ -0,0 +1,406 @@
+#ifndef OSMIUM_RELATIONS_MEMBERS_DATABASE_HPP
+#define OSMIUM_RELATIONS_MEMBERS_DATABASE_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <limits>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+#include <osmium/osm/object.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/types.hpp>
+#include <osmium/relations/relations_database.hpp>
+#include <osmium/storage/item_stash.hpp>
+#include <osmium/util/iterator.hpp>
+
+namespace osmium {
+
+    namespace relations {
+
+        /**
+         * This is the parent class for the MembersDatabase class. All the
+         * functionality which doesn't depend on the template parameter used
+         * in derived databases is contained in this class.
+         *
+         * Usually you want to use the MembersDatabase class only.
+         */
+        class MembersDatabaseCommon {
+
+            struct element {
+
+                /**
+                 * Special value used for member_num to mark the element as
+                 * removed.
+                 */
+                static const size_t removed_value = std::numeric_limits<std::size_t>::max();
+
+                /**
+                 * Object ID of this relation member. Can be a node, way,
+                 * or relation ID. It depends on the database in which this
+                 * object is stored which kind of object is referenced here.
+                 */
+                osmium::object_id_type member_id;
+
+                /**
+                 * Position of this member in the parent relation.
+                 */
+                std::size_t member_num;
+
+                /**
+                 * Position of the parent relation in the relations database.
+                 */
+                std::size_t relation_pos;
+
+                /**
+                 * Handle to the stash where the object is stored.
+                 *
+                 * The default value is the invalid one signifying that the
+                 * object hasn't been found yet.
+                 */
+                osmium::ItemStash::handle_type object_handle;
+
+                explicit element(std::size_t rel_pos, osmium::object_id_type memb_id, std::size_t memb_num) noexcept :
+                    member_id(memb_id),
+                    member_num(memb_num),
+                    relation_pos(rel_pos) {
+                }
+
+                /**
+                 * This constructor is used to create dummy elements that
+                 * can be compared to the elements in a vector using the
+                 * equal_range algorithm.
+                 */
+                explicit element(osmium::object_id_type m_id) noexcept :
+                    member_id(m_id),
+                    member_num(0),
+                    relation_pos(0) {
+                }
+
+                bool is_removed() const noexcept {
+                    return member_num == removed_value;
+                }
+
+                void remove() noexcept {
+                    member_num = removed_value;
+                }
+
+                bool operator<(const element& other) const noexcept {
+                    return std::tie(member_id, member_num, relation_pos) <
+                           std::tie(other.member_id, other.member_num, other.relation_pos);
+                }
+
+            }; // struct element
+
+            // comparison function only comparing member_id.
+            struct compare_member_id {
+                bool operator()(const element& a, const element& b) const noexcept {
+                    return a.member_id < b.member_id;
+                }
+            };
+
+            std::vector<element> m_elements;
+
+        protected:
+
+            osmium::ItemStash& m_stash;
+            osmium::relations::RelationsDatabase& m_relations_db;
+
+#ifndef NDEBUG
+            // This is used only in debug builds to make sure the
+            // prepare_for_lookup() function is called at the right place.
+            bool m_init_phase = true;
+#endif
+
+            using iterator = std::vector<element>::iterator;
+            using const_iterator = std::vector<element>::const_iterator;
+
+            iterator_range<iterator> find(osmium::object_id_type id) {
+                return make_range(std::equal_range(m_elements.begin(), m_elements.end(), element{id}, compare_member_id{}));
+            }
+
+            iterator_range<const_iterator> find(osmium::object_id_type id) const {
+                return make_range(std::equal_range(m_elements.cbegin(), m_elements.cend(), element{id}, compare_member_id{}));
+            }
+
+            static typename iterator_range<iterator>::iterator::difference_type count_not_removed(const iterator_range<iterator>& range) noexcept {
+                return std::count_if(range.begin(), range.end(), [](const element& elem) {
+                    return !elem.is_removed();
+                });
+            }
+
+            void add_object(const osmium::OSMObject& object, iterator_range<iterator>& range) {
+                const auto handle = m_stash.add_item(object);
+                for (auto& elem : range) {
+                    elem.object_handle = handle;
+                }
+            }
+
+            MembersDatabaseCommon(osmium::ItemStash& stash, osmium::relations::RelationsDatabase& relations_db) :
+                m_elements(),
+                m_stash(stash),
+                m_relations_db(relations_db) {
+            }
+
+        public:
+
+            /**
+             * Return an estimate of the number of bytes currently needed
+             * for the MembersDatabase. This does NOT include the memory used
+             * in the stash. Used for debugging.
+             */
+            std::size_t used_memory() const noexcept {
+                return sizeof(element) * m_elements.capacity() +
+                       sizeof(MembersDatabaseCommon);
+            }
+
+            /**
+             * The number of members tracked in the database. Includes
+             * members tracked, but not found yet, members found and members
+             * marked as removed.
+             *
+             * Complexity: Constant.
+             */
+            std::size_t size() const noexcept {
+                return m_elements.size();
+            }
+
+            /**
+             * Result from the count() function.
+             */
+            struct counts {
+                /// The number of members tracked and not found yet.
+                std::size_t tracked   = 0;
+                /// The number of members tracked and found already.
+                std::size_t available = 0;
+                /// The number of members that were tracked, found and then removed because of a completed relation.
+                std::size_t removed   = 0;
+            };
+
+            /**
+             * Counts the number of members in different states. Usually only
+             * used for testing and debugging.
+             *
+             * Complexity: Linear in the number of members tracked.
+             */
+            counts count() const noexcept {
+                counts c;
+
+                for (const auto& elem : m_elements) {
+                    if (elem.is_removed()) {
+                        ++c.removed;
+                    } else if (elem.object_handle.valid()) {
+                        ++c.available;
+                    } else {
+                        ++c.tracked;
+                    }
+                }
+
+                return c;
+            }
+
+            /**
+             * Tell the database that you are interested in an object with
+             * the specified id and that it is a member of the given relation
+             * (as specified through the relation handle).
+             *
+             * @param rel_handle Relation this object is a member of.
+             * @param member_id Id of an object of type TObject.
+             * @param member_num This is the nth member in the relation.
+             */
+            void track(RelationHandle& rel_handle, osmium::object_id_type member_id, std::size_t member_num) {
+                assert(m_init_phase && "Can not call MembersDatabase::track() after MembersDatabase::prepare_for_lookup().");
+                assert(rel_handle.relation_database() == &m_relations_db);
+                m_elements.emplace_back(rel_handle.pos(), member_id, member_num);
+                rel_handle.increment_members();
+            }
+
+            /**
+             * Prepare the database for lookup. Call this function after
+             * calling track() for all objects needed and before adding
+             * the first object with add() or querying the first object
+             * with get(). You can only call this function once.
+             */
+            void prepare_for_lookup() {
+                assert(m_init_phase && "Can not call MembersDatabase::prepare_for_lookup() twice.");
+                std::sort(m_elements.begin(), m_elements.end());
+#ifndef NDEBUG
+                m_init_phase = false;
+#endif
+            }
+
+            /**
+             * Remove the entry with the specified member_id and relation_id
+             * from the database. If the entry doesn't exist, nothing happens.
+             */
+            void remove(osmium::object_id_type member_id, osmium::object_id_type relation_id) {
+                const auto range = find(member_id);
+
+                if (range.empty()) {
+                    return;
+                }
+
+                // If this is the last time this object was needed, remove it
+                // from the stash.
+                if (count_not_removed(range) == 1) {
+                    m_stash.remove_item(range.begin()->object_handle);
+                }
+
+                for (auto& elem : range) {
+                    if (!elem.is_removed() && relation_id == m_relations_db[elem.relation_pos]->id()) {
+                        elem.remove();
+                        break;
+                    }
+                }
+            }
+
+            /**
+             * Find the object with the specified id in the database and
+             * return a pointer to it. Returns nullptr if there is no object
+             * with that id in the database.
+             *
+             * Complexity: Logarithmic in the number of members tracked (as
+             *             returned by size()).
+             */
+            const osmium::OSMObject* get_object(osmium::object_id_type id) const {
+                assert(!m_init_phase && "Call MembersDatabase::prepare_for_lookup() before calling get_object().");
+                const auto range = find(id);
+                if (range.empty()) {
+                    return nullptr;
+                }
+                const auto handle = range.begin()->object_handle;
+                if (handle.valid()) {
+                    return &m_stash.get<osmium::OSMObject>(handle);
+                }
+                return nullptr;
+            }
+
+        }; // class MembersDatabaseCommon
+
+        /**
+         * A MembersDatabase is used together with a RelationsDatabase to
+         * bring a relation and their members together. It tracks all members
+         * of a specific type needed to complete a relation.
+         *
+         * More documentation is in the MembersDatabaseCommon parent class
+         * which contains all the pieces that aren't dependent on the
+         * template parameter.
+         *
+         * @tparam TObject The object type stores in the members database.
+         *                 Can be osmium::Node, Way, or Relation.
+         */
+        template <typename TObject>
+        class MembersDatabase : public MembersDatabaseCommon {
+
+            static_assert(std::is_base_of<osmium::OSMObject, TObject>::value, "TObject must be osmium::Node, Way, or Relation.");
+
+        public:
+
+            /**
+             * Construct a MembersDatabase.
+             *
+             * @param stash Reference to an ItemStash object. All member objects
+             *              will be stored in this stash. It must be available
+             *              until the MembersDatabase is destroyed.
+             * @param relation_db The RelationsDatabase where relations are
+             *                    stored. Usually it will use the same ItemStash
+             *                    as the MembersDatabase.
+             */
+            MembersDatabase(osmium::ItemStash& stash, osmium::relations::RelationsDatabase& relation_db) :
+                MembersDatabaseCommon(stash, relation_db) {
+            }
+
+            /**
+             * Add the specified object to the database.
+             *
+             * @param object Object to add.
+             * @param func If the object is the last member to complete a
+             *             relation, this function is called with the relation
+             *             as a parameter.
+             * @returns true if the object was actually added, false if no
+             *          relation needed this object.
+             */
+            template <typename TFunc>
+            bool add(const TObject& object, TFunc&& func) {
+                assert(!m_init_phase && "Call MembersDatabase::prepare_for_lookup() before calling add().");
+                auto range = find(object.id());
+
+                if (range.empty()) {
+                    // No relation needs this object.
+                    return false;
+                }
+
+                // At least one relation needs this object. Store it and
+                // "tell" all relations.
+                add_object(object, range);
+
+                for (auto& elem : range) {
+                    assert(!elem.is_removed());
+                    assert(elem.member_id == object.id());
+
+                    auto rel_handle = m_relations_db[elem.relation_pos];
+                    assert(elem.member_num < rel_handle->members().size());
+                    rel_handle.decrement_members();
+
+                    if (rel_handle.has_all_members()) {
+                        func(rel_handle);
+                    }
+                }
+
+                return true;
+            }
+
+            /**
+             * Find the object with the specified id in the database and
+             * return a pointer to it. Returns nullptr if there is no object
+             * with that id in the database.
+             *
+             * Complexity: Logarithmic in the number of members tracked (as
+             *             returned by size()).
+             */
+            const TObject* get(osmium::object_id_type id) const {
+                assert(!m_init_phase && "Call MembersDatabase::prepare_for_lookup() before calling get().");
+                return static_cast<const TObject*>(get_object(id));
+            }
+
+        }; // class MembersDatabase
+
+    } // namespace relations
+
+} // namespace osmium
+
+#endif // OSMIUM_RELATIONS_MEMBERS_DATABASE_HPP
diff --git a/include/osmium/relations/relations_database.hpp b/include/osmium/relations/relations_database.hpp
new file mode 100644
index 0000000..80dd102
--- /dev/null
+++ b/include/osmium/relations/relations_database.hpp
@@ -0,0 +1,335 @@
+#ifndef OSMIUM_RELATIONS_RELATIONS_DATABASE_HPP
+#define OSMIUM_RELATIONS_RELATIONS_DATABASE_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+#include <osmium/osm/relation.hpp>
+#include <osmium/storage/item_stash.hpp>
+
+namespace osmium {
+
+    namespace relations {
+
+        class RelationHandle;
+
+        /**
+         * The RelationsDatabase is used for bringing relations and their
+         * members together. It stores the relations in memory and keeps
+         * track of how many members are needed to "complete" the relation.
+         * It is intended to work together with the MembersDatabase template
+         * class and usually used by relations manager classes.
+         *
+         * To access relations stored in the database a RelationHandle is
+         * used. It is returned from the add() function. The handle is used
+         * for all operations on the database contents, such as accessing
+         * the stored relation, incrementing the member count or removing a
+         * relation from the database.
+         *
+         * From the handle a "position" can be accessed, which, together with
+         * the database object, can be turned into a handle again. The position
+         * alone is smaller than the handle, so it can be stored elsewhere more
+         * efficiently. Specifically this is used in the MembersDatabase.
+         *
+         * @code
+         *    osmium::ItemStash stash;
+         *    osmium::relations::RelationsDatabase db{stash};
+         *    auto handle = db.add(relation);
+         *    auto pos = handle.pos();
+         *    auto second_handle = db[pos];
+         * @endcode
+         *
+         * Now the `handle` and `second_handle` refer to the same relation.
+         *
+         * See the RelationHandle for information about what you can do with
+         * it.
+         */
+        class RelationsDatabase {
+
+            friend class RelationHandle;
+
+            struct element {
+
+                /// A handle to the relation in the ItemStash.
+                osmium::ItemStash::handle_type handle;
+
+                /**
+                 * The number of members still needed before the relation is
+                 * complete. This will be set to the number of members we are
+                 * interested in (which can be all members of a relation or
+                 * a subset of them) and then count down for every member we
+                 * find. When it is 0, the relation is complete.
+                 */
+                std::size_t members;
+
+            }; // struct element
+
+            osmium::ItemStash& m_stash;
+            std::vector<element> m_elements;
+
+            osmium::Relation& get_relation(std::size_t pos) {
+                assert(pos < m_elements.size());
+                return m_stash.get<osmium::Relation>(m_elements[pos].handle);
+            }
+
+            /**
+             * Access the number of members of the entry at the specified
+             * position. This returns a reference so it can be changed.
+             */
+            std::size_t& members(std::size_t pos) noexcept {
+                return m_elements[pos].members;
+            }
+
+            void remove(std::size_t pos) {
+                auto& elem = m_elements[pos];
+                m_stash.remove_item(elem.handle);
+                elem = element{osmium::ItemStash::handle_type{}, 0};
+            }
+
+        public:
+
+            /**
+             * Construct a RelationsDatabase.
+             *
+             * @param stash Reference to an ItemStash object. All relations
+             *              will be stored in this stash. It must be available
+             *              until the RelationsDatabase is destroyed.
+             */
+            explicit RelationsDatabase(osmium::ItemStash& stash) :
+                m_stash(stash) {
+            }
+
+            /**
+             * Return an estimate of the number of bytes currently needed for
+             * the RelationsDatabase. This does NOT include the memory used
+             * in the stash. Used for debugging.
+             *
+             * Complexity: Constant.
+             */
+            std::size_t used_memory() const noexcept {
+                return sizeof(element) * m_elements.capacity() +
+                       sizeof(RelationsDatabase);
+            }
+
+            /**
+             * The number of relations stored in the database. Includes
+             * relations marked as removed.
+             *
+             * Complexity: Constant.
+             */
+            std::size_t size() const noexcept {
+                return m_elements.size();
+            }
+
+            /**
+             * Insert a relation into the database. The relation is copied
+             * into the stash.
+             *
+             * Complexity: Amortized constant.
+             *
+             * @param relation The relation to be copied into the database.
+             * @returns A handle to the relation.
+             */
+            RelationHandle add(const osmium::Relation& relation);
+
+            /**
+             * Return a handle to the relation at the specified position in
+             * the database.
+             *
+             * Complexity: Constant.
+             */
+            RelationHandle operator[](std::size_t pos) noexcept;
+
+            /**
+             * Return the number of non-removed relations in the database.
+             *
+             * Complexity: Linear in the number of relations (as returned
+             *             by size()).
+             */
+            std::size_t count_relations() const noexcept {
+                return std::count_if(m_elements.cbegin(), m_elements.cend(), [&](const element& elem) {
+                    return elem.handle.valid();
+                });
+            }
+
+            /**
+             * Iterate over all (not-removed) relations in the database.
+             *
+             * @tparam TFunc Function with type void(const RelationHandle&).
+             * @param func Callback function which will be called for every
+             *             not-removed relation with a RelationHandle.
+             */
+            template <typename TFunc>
+            void for_each_relation(TFunc&& func);
+
+        }; // RelationsDatabase
+
+        /**
+         * A RelationHandle is used to access elements in a RelationsDatabase.
+         *
+         * RelationHandles can not be created by user code, they are only
+         * given out by a RelationsDatabase object.
+         */
+        class RelationHandle {
+
+            friend class RelationsDatabase;
+
+            RelationsDatabase* m_relation_database;
+            std::size_t m_pos;
+
+            RelationHandle(RelationsDatabase* relation_database, std::size_t pos) :
+                m_relation_database(relation_database),
+                m_pos(pos) {
+            }
+
+        public:
+
+            /**
+             * The RelationsDatabase this handle refers to.
+             */
+            RelationsDatabase* relation_database() const noexcept {
+                return m_relation_database;
+            }
+
+            /**
+             * The position of the element in the RelationsDatabase. Use the
+             * RelationsDatabase::operator[] to get the handle back from this
+             * position:
+             * @code
+             * auto pos = handle.pos();
+             * auto second_handle = relation_db[pos];
+             * @endcode
+             */
+            std::size_t pos() const noexcept {
+                return m_pos;
+            }
+
+            /**
+             * Access the relation stored in the database.
+             */
+            Relation& operator*() {
+                return m_relation_database->get_relation(m_pos);
+            }
+
+            /**
+             * Access the relation stored in the database.
+             */
+            const Relation& operator*() const {
+                return m_relation_database->get_relation(m_pos);
+            }
+
+            /**
+             * Call a function on the relation stored in the database.
+             */
+            Relation* operator->() {
+                return &m_relation_database->get_relation(m_pos);
+            }
+
+            /**
+             * Call a function on the relation stored in the database.
+             */
+            const Relation* operator->() const {
+                return &m_relation_database->get_relation(m_pos);
+            }
+
+            /**
+             * Remove the relation referred to by this handle from the database.
+             * All handles referring to this database element become invalid.
+             */
+            void remove() {
+                m_relation_database->remove(pos());
+            }
+
+            /**
+             * Set the number of relation members that we want to track.
+             */
+            void set_members(std::size_t value) noexcept {
+                m_relation_database->members(m_pos) = value;
+            }
+
+            /**
+             * Increment the number of relation members that we want to track.
+             */
+            void increment_members() noexcept {
+                ++(m_relation_database->members(m_pos));
+            }
+
+            /**
+             * Decrement the number of relation members that we want to track.
+             *
+             * @pre @code has_all_members() == false @endcode
+             */
+            void decrement_members() noexcept {
+                assert(m_relation_database->members(m_pos) > 0);
+                --(m_relation_database->members(m_pos));
+            }
+
+            /**
+             * Do we have all members? This is true if the number of tracked
+             * members is zero.
+             */
+            bool has_all_members() const noexcept {
+                return m_relation_database->members(m_pos) == 0;
+            }
+
+        }; // class RelationHandle
+
+        inline RelationHandle RelationsDatabase::operator[](std::size_t pos) noexcept {
+            assert(pos < m_elements.size());
+            return {this, pos};
+        }
+
+        inline RelationHandle RelationsDatabase::add(const osmium::Relation& relation) {
+            m_elements.push_back(element{m_stash.add_item(relation), 0});
+            return {this, m_elements.size() - 1};
+        }
+
+        template <typename TFunc>
+        void RelationsDatabase::for_each_relation(TFunc&& func) {
+            for (std::size_t pos = 0; pos < m_elements.size(); ++pos) {
+                if (m_elements[pos].handle.valid()) {
+                    std::forward<TFunc>(func)(RelationHandle{this, pos});
+                }
+            }
+        }
+
+    } // namespace relations
+
+} // namespace osmium
+
+#endif // OSMIUM_RELATIONS_RELATIONS_DATABASE_HPP
diff --git a/include/osmium/relations/relations_manager.hpp b/include/osmium/relations/relations_manager.hpp
new file mode 100644
index 0000000..1831cb6
--- /dev/null
+++ b/include/osmium/relations/relations_manager.hpp
@@ -0,0 +1,567 @@
+#ifndef OSMIUM_RELATIONS_RELATIONS_MANAGER_HPP
+#define OSMIUM_RELATIONS_RELATIONS_MANAGER_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include <osmium/handler.hpp>
+#include <osmium/handler/check_order.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/memory/callback_buffer.hpp>
+#include <osmium/osm/item_type.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/tag.hpp>
+#include <osmium/osm/way.hpp>
+#include <osmium/relations/manager_util.hpp>
+#include <osmium/relations/members_database.hpp>
+#include <osmium/relations/relations_database.hpp>
+#include <osmium/storage/item_stash.hpp>
+#include <osmium/tags/taglist.hpp>
+#include <osmium/tags/tags_filter.hpp>
+
+namespace osmium {
+
+    namespace relations {
+
+        /**
+         * This is a base class of the RelationsManager class template. It
+         * contains databases for the relations and the members that we need
+         * to keep track of and handles the ouput buffer. Unlike the
+         * RelationsManager class template this is a plain class.
+         *
+         * Usually it is better to use the RelationsManager class template
+         * as a basis for your code, but you can also use this class if you
+         * have special needs.
+         */
+        class RelationsManagerBase : public osmium::handler::Handler {
+
+            // All relations and members we are interested in will be kept
+            // in here.
+            osmium::ItemStash m_stash;
+
+            /// Database of all relations we are interested in.
+            relations::RelationsDatabase m_relations_db;
+
+            /// Databases of all members we are interested in.
+            relations::MembersDatabase<osmium::Node>     m_member_nodes_db;
+            relations::MembersDatabase<osmium::Way>      m_member_ways_db;
+            relations::MembersDatabase<osmium::Relation> m_member_relations_db;
+
+            /// Output buffer.
+            osmium::memory::CallbackBuffer m_output;
+
+        public:
+
+            RelationsManagerBase() :
+                m_stash(),
+                m_relations_db(m_stash),
+                m_member_nodes_db(m_stash, m_relations_db),
+                m_member_ways_db(m_stash, m_relations_db),
+                m_member_relations_db(m_stash, m_relations_db),
+                m_output() {
+            }
+
+            /// Access the internal RelationsDatabase.
+            osmium::relations::RelationsDatabase& relations_database() noexcept {
+                return m_relations_db;
+            }
+
+            /// Access the internal database containing member nodes.
+            osmium::relations::MembersDatabase<osmium::Node>& member_nodes_database() noexcept {
+                return m_member_nodes_db;
+            }
+
+            /// Access the internal database containing member nodes.
+            const osmium::relations::MembersDatabase<osmium::Node>& member_nodes_database() const noexcept {
+                return m_member_nodes_db;
+            }
+
+            /// Access the internal database containing member ways.
+            osmium::relations::MembersDatabase<osmium::Way>& member_ways_database() noexcept {
+                return m_member_ways_db;
+            }
+
+            /// Access the internal database containing member ways.
+            const osmium::relations::MembersDatabase<osmium::Way>& member_ways_database() const noexcept {
+                return m_member_ways_db;
+            }
+
+            /// Access the internal database containing member relations.
+            osmium::relations::MembersDatabase<osmium::Relation>& member_relations_database() noexcept {
+                return m_member_relations_db;
+            }
+
+            /// Access the internal database containing member relations.
+            const osmium::relations::MembersDatabase<osmium::Relation>& member_relations_database() const noexcept {
+                return m_member_relations_db;
+            }
+
+            /**
+             * Access the internal database containing members of the
+             * specified type (non-const version of this function).
+             *
+             * @param type osmium::item_type::node, way, or relation.
+             */
+            relations::MembersDatabaseCommon& member_database(osmium::item_type type) {
+                switch (type) {
+                    case osmium::item_type::node:
+                        return m_member_nodes_db;
+                    case osmium::item_type::way:
+                        return m_member_ways_db;
+                    case osmium::item_type::relation:
+                        return m_member_relations_db;
+                    default:
+                        break;
+                }
+                throw std::logic_error{"Should not be here."};
+            }
+
+            /**
+             * Access the internal database containing members of the
+             * specified type (const version of this function).
+             *
+             * @param type osmium::item_type::node, way, or relation.
+             */
+            const relations::MembersDatabaseCommon& member_database(osmium::item_type type) const {
+                switch (type) {
+                    case osmium::item_type::node:
+                        return m_member_nodes_db;
+                    case osmium::item_type::way:
+                        return m_member_ways_db;
+                    case osmium::item_type::relation:
+                        return m_member_relations_db;
+                    default:
+                        break;
+                }
+                throw std::logic_error{"Should not be here."};
+            }
+
+            /**
+             * Get member object from relation member.
+             *
+             * @returns A pointer to the member object if it is available.
+             *          Returns nullptr otherwise.
+             */
+            const osmium::OSMObject* get_member_object(const osmium::RelationMember& member) const noexcept {
+                if (member.ref() == 0) {
+                    return nullptr;
+                }
+                return member_database(member.type()).get_object(member.ref());
+            }
+
+            /**
+             * Get node with specified ID from members database.
+             *
+             * @param id The node ID we are looking for.
+             * @returns A pointer to the member node if it is available.
+             *          Returns nullptr otherwise.
+             */
+            const osmium::Node* get_member_node(osmium::object_id_type id) const noexcept {
+                if (id == 0) {
+                    return nullptr;
+                }
+                return member_nodes_database().get(id);
+            }
+
+            /**
+             * Get way with specified ID from members database.
+             *
+             * @param id The way ID we are looking for.
+             * @returns A pointer to the member way if it is available.
+             *          Returns nullptr otherwise.
+             */
+            const osmium::Way* get_member_way(osmium::object_id_type id) const noexcept {
+                if (id == 0) {
+                    return nullptr;
+                }
+                return member_ways_database().get(id);
+            }
+
+            /**
+             * Get relation with specified ID from members database.
+             *
+             * @param id The relation ID we are looking for.
+             * @returns A pointer to the member relation if it is available.
+             *          Returns nullptr otherwise.
+             */
+            const osmium::Relation* get_member_relation(osmium::object_id_type id) const noexcept {
+                if (id == 0) {
+                    return nullptr;
+                }
+                return member_relations_database().get(id);
+            }
+
+            /**
+             * Sort the members databases to prepare them for reading. Usually
+             * this is called between the first and second pass reading through
+             * an OSM data file.
+             */
+            void prepare_for_lookup() {
+                m_member_nodes_db.prepare_for_lookup();
+                m_member_ways_db.prepare_for_lookup();
+                m_member_relations_db.prepare_for_lookup();
+            }
+
+            /**
+             * Return the memory used by different components of the manager.
+             */
+            relations_manager_memory_usage used_memory() const noexcept {
+                return {
+                    m_relations_db.used_memory(),
+                      m_member_nodes_db.used_memory()
+                    + m_member_ways_db.used_memory()
+                    + m_member_relations_db.used_memory(),
+                    m_stash.used_memory()
+                };
+            }
+
+            /// Access the output buffer.
+            osmium::memory::Buffer& buffer() noexcept {
+                return m_output.buffer();
+            }
+
+            /// Set the callback called when the output buffer is full.
+            void set_callback(const std::function<void(osmium::memory::Buffer&&)>& callback) {
+                m_output.set_callback(callback);
+            }
+
+            /// Flush the output buffer.
+            void flush_output() {
+                m_output.flush();
+            }
+
+            /// Flush the output buffer if it is full.
+            void possibly_flush() {
+                m_output.possibly_flush();
+            }
+
+            /// Return the contents of the output buffer.
+            osmium::memory::Buffer read() {
+                return m_output.read();
+            }
+
+        }; // class RelationsManagerBase
+
+        /**
+         * This is a base class for RelationManager classes. It keeps track of
+         * all interesting relations and all interesting members of those
+         * relations. When all members are available it calls code to handle
+         * the completed relation.
+         *
+         * This class is intended as a base class for classes that handle
+         * specific types of relations. Derive from this class, implement
+         * the complete_relation() function and overwrite certain other
+         * functions as needed.
+         *
+         * @tparam TManager The derived class (Uses CRTP).
+         * @tparam TNodes Are we interested in member nodes?
+         * @tparam TWays Are we interested in member ways?
+         * @tparam TRelations Are we interested in member relations?
+         * @tparam TCheckOrder Should the order of the input data be checked?
+         *
+         * @pre The Ids of all objects must be unique in the input data.
+         */
+        template <typename TManager, bool TNodes, bool TWays, bool TRelations, bool TCheckOrder = true>
+        class RelationsManager : public RelationsManagerBase {
+
+            using check_order_handler = typename std::conditional<TCheckOrder, osmium::handler::CheckOrder, osmium::handler::Handler>::type;
+
+            check_order_handler m_check_order_handler;
+
+            SecondPassHandler<RelationsManager> m_handler_pass2;
+
+            static bool wanted_type(osmium::item_type type) noexcept {
+                return (TNodes     && type == osmium::item_type::node) ||
+                       (TWays      && type == osmium::item_type::way) ||
+                       (TRelations && type == osmium::item_type::relation);
+            }
+
+            /**
+             * This method is called from the first pass handler for every
+             * relation in the input, to check whether it should be kept.
+             *
+             * Overwrite this method in a derived class to only add relations
+             * you are interested in, for instance depending on the type tag.
+             * Storing relations takes a lot of memory, so it makes sense to
+             * filter this as much as possible.
+             */
+            bool new_relation(const osmium::Relation& /*relation*/) const noexcept {
+                return true;
+            }
+
+            /**
+             * This method is called for every member of every relation that
+             * will be kept. It should decide if the member is interesting or
+             * not and return true or false to signal that. Only interesting
+             * members are later added to the relation.
+             *
+             * Overwrite this method in a derived class. In the
+             * MultipolygonManager class this is used for instance to only
+             * keep members of type way and ignore all others.
+             */
+            bool new_member(const osmium::Relation& /*relation*/, const osmium::RelationMember& /*member*/, std::size_t /*n*/) const noexcept {
+                return true;
+            }
+
+            /**
+             * This method is called for each complete relation, ie when
+             * all members you have expressed interest in are available.
+             *
+             * You have to overwrite this in a derived class.
+             */
+            void complete_relation(const osmium::Relation& /*relation*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all nodes during the second pass
+             * before the relation member handling.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void before_node(const osmium::Node& /*node*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all nodes that are not a member of
+             * any relation.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void node_not_in_any_relation(const osmium::Node& /*node*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all nodes during the second pass
+             * after the relation member handling.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void after_node(const osmium::Node& /*node*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all ways during the second pass
+             * before the relation member handling.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void before_way(const osmium::Way& /*way*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all ways that are not a member of
+             * any relation.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void way_not_in_any_relation(const osmium::Way& /*way*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all ways during the second pass
+             * after the relation member handling.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void after_way(const osmium::Way& /*way*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all relations during the second pass
+             * before the relation member handling.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void before_relation(const osmium::Relation& /*relation*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all relations that are not a member of
+             * any relation.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void relation_not_in_any_relation(const osmium::Relation& /*relation*/) const noexcept {
+            }
+
+            /**
+             * This method is called for all relations during the second pass
+             * after the relation member handling.
+             *
+             * Overwrite this method in a derived class if you are interested
+             * in this.
+             */
+            void after_relation(const osmium::Relation& /*relation*/) const noexcept {
+            }
+
+            TManager& derived() noexcept {
+                return *static_cast<TManager*>(this);
+            }
+
+            void handle_complete_relation(RelationHandle& rel_handle) {
+                derived().complete_relation(*rel_handle);
+                possibly_flush();
+
+                for (const auto& member : rel_handle->members()) {
+                    if (member.ref() != 0) {
+                        member_database(member.type()).remove(member.ref(), rel_handle->id());
+                    }
+                }
+
+                rel_handle.remove();
+            }
+
+        public:
+
+            RelationsManager() :
+                RelationsManagerBase(),
+                m_check_order_handler(),
+                m_handler_pass2(*this) {
+            }
+
+            /**
+             * Return reference to second pass handler.
+             */
+            SecondPassHandler<RelationsManager>& handler(const std::function<void(osmium::memory::Buffer&&)>& callback = nullptr) {
+                set_callback(callback);
+                return m_handler_pass2;
+            }
+
+            /**
+             * Add the specified relation to the list of relations we want to
+             * build. This calls the new_relation() and new_member()
+             * functions to actually decide what to keep.
+             *
+             * This member function is named relation() so the manager can
+             * be used as a handler for the first pass through a data file.
+             *
+             * @param relation Relation we might want to build.
+             */
+            void relation(const osmium::Relation& relation) {
+                if (derived().new_relation(relation)) {
+                    auto rel_handle = relations_database().add(relation);
+
+                    std::size_t n = 0;
+                    for (auto& member : rel_handle->members()) {
+                        if (wanted_type(member.type()) &&
+                            derived().new_member(relation, member, n)) {
+                            member_database(member.type()).track(rel_handle, member.ref(), n);
+                        } else {
+                            member.set_ref(0); // set member id to zero to indicate we are not interested
+                        }
+                        ++n;
+                    }
+                }
+            }
+
+            void handle_node(const osmium::Node& node) {
+                if (TNodes) {
+                    m_check_order_handler.node(node);
+                    derived().before_node(node);
+                    const bool added = member_nodes_database().add(node, [this](RelationHandle& rel_handle) {
+                        handle_complete_relation(rel_handle);
+                    });
+                    if (! added) {
+                        derived().node_not_in_any_relation(node);
+                    }
+                    derived().after_node(node);
+                    possibly_flush();
+                }
+            }
+
+            void handle_way(const osmium::Way& way) {
+                if (TWays) {
+                    m_check_order_handler.way(way);
+                    derived().before_way(way);
+                    const bool added = member_ways_database().add(way, [this](RelationHandle& rel_handle) {
+                        handle_complete_relation(rel_handle);
+                    });
+                    if (! added) {
+                        derived().way_not_in_any_relation(way);
+                    }
+                    derived().after_way(way);
+                    possibly_flush();
+                }
+            }
+
+            void handle_relation(const osmium::Relation& relation) {
+                if (TRelations) {
+                    m_check_order_handler.relation(relation);
+                    derived().before_relation(relation);
+                    const bool added = member_relations_database().add(relation, [this](RelationHandle& rel_handle) {
+                        handle_complete_relation(rel_handle);
+                    });
+                    if (! added) {
+                        derived().relation_not_in_any_relation(relation);
+                    }
+                    derived().after_relation(relation);
+                    possibly_flush();
+                }
+            }
+
+            /**
+             * Call this function it will call your function back for every
+             * incomplete relation, that is all relations that have missing
+             * members in the input data. Usually you call this only after
+             * your second pass through the data if you are interested in
+             * any relations that have all or some of their members missing
+             * in the input data.
+             */
+            template <typename TFunc>
+            void for_each_incomplete_relation(TFunc&& func) {
+                relations_database().for_each_relation(std::forward<TFunc>(func));
+            }
+
+        }; // class RelationsManager
+
+    } // namespace relations
+
+} // namespace osmium
+
+#endif // OSMIUM_RELATIONS_RELATIONS_MANAGER_HPP
diff --git a/include/osmium/storage/item_stash.hpp b/include/osmium/storage/item_stash.hpp
new file mode 100644
index 0000000..b49d88b
--- /dev/null
+++ b/include/osmium/storage/item_stash.hpp
@@ -0,0 +1,342 @@
+#ifndef OSMIUM_ITEM_STASH_HPP
+#define OSMIUM_ITEM_STASH_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2017 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cassert>
+#include <cstdlib>
+#include <limits>
+#include <ostream>
+#include <vector>
+
+#ifdef OSMIUM_ITEM_STORAGE_GC_DEBUG
+# include <iostream>
+# include <chrono>
+#endif
+
+#include <osmium/memory/buffer.hpp>
+#include <osmium/memory/item.hpp>
+
+namespace osmium {
+
+    /**
+     * Class for storing OSM data in memory. Any osmium::memory::Item can be
+     * added to the stash and it will be copied into its internal Buffer. To
+     * access the item again, an opaque handle is used.
+     */
+    class ItemStash {
+
+    public:
+
+        /**
+         * This is the type of the handle returned by the add_item() call.
+         * It is used to access the item again with get_item() or get<>()
+         * or erase it with remove_item().
+         *
+         * There is one special handle, the invalid handle. It can be created
+         * by calling the default constructor. You can use it for instance to
+         * mark removed objects in your data structures. Valid handles can
+         * only be constructed by the ItemStash class.
+         */
+        class handle_type {
+
+            friend class ItemStash;
+
+            std::size_t value;
+
+            explicit handle_type(std::size_t new_value) noexcept :
+                value(new_value) {
+                assert(new_value > 0);
+            }
+
+        public:
+
+            /// The defalt constructor creates an invalid handle.
+            handle_type() noexcept :
+                value(0) {
+            }
+
+            /// Is this a valid handle?
+            bool valid() const noexcept {
+                return value != 0;
+            }
+
+            /**
+             * Print the handle for debugging purposes. An invalid handle
+             * will be printed as the single letter '-'. A valid handle will
+             * be printed as a unique (for an ItemStash object) number.
+             */
+            template <typename TChar, typename TTraits>
+            friend inline std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const ItemStash::handle_type& handle) {
+                if (handle.valid()) {
+                    out << handle.value;
+                } else {
+                    out << '-';
+                }
+                return out;
+            }
+
+        }; // class handle_type
+
+    private:
+
+        static constexpr const std::size_t initial_buffer_size = 1024 * 1024;
+        static constexpr const std::size_t removed_item_offset = std::numeric_limits<std::size_t>::max();
+
+        osmium::memory::Buffer m_buffer;
+        std::vector<std::size_t> m_index;
+        std::size_t m_count_items = 0;
+        std::size_t m_count_removed = 0;
+#ifdef OSMIUM_ITEM_STORAGE_GC_DEBUG
+        int64_t m_gc_time = 0;
+#endif
+
+        class cleanup_helper {
+
+            std::vector<std::size_t>& m_index;
+            std::size_t m_pos = 0;
+
+        public:
+
+            explicit cleanup_helper(std::vector<std::size_t>& index) :
+                m_index(index) {
+            }
+
+            void moving_in_buffer(std::size_t old_offset, std::size_t new_offset) {
+                while (m_index[m_pos] != old_offset) {
+                    ++m_pos;
+                    assert(m_pos < m_index.size());
+                }
+                m_index[m_pos] = new_offset;
+                ++m_pos;
+            }
+
+        }; // cleanup_helper
+
+        std::size_t& get_item_offset_ref(handle_type handle) noexcept {
+            assert(handle.valid() && "handle must be valid");
+            assert(handle.value <= m_index.size());
+            auto& offset = m_index[handle.value - 1];
+            assert(offset != removed_item_offset);
+            assert(offset < m_buffer.committed());
+            return offset;
+        }
+
+        std::size_t get_item_offset(handle_type handle) const noexcept {
+            assert(handle.valid() && "handle must be valid");
+            assert(handle.value <= m_index.size());
+            const auto& offset = m_index[handle.value - 1];
+            assert(offset != removed_item_offset);
+            assert(offset < m_buffer.committed());
+            return offset;
+        }
+
+        // This function decides whether it makes sense to garbage collect the
+        // database. The values here are the result of some experimentation
+        // with real data. We need to balance the memory use with the time
+        // spent on garbage collecting. We don't need to garbage collect if
+        // there is enough space in the buffer anyway (*4). On the other hand,
+        // if there aren't enough removed objects we would just call the
+        // garbage collection again and again, then it is better to let the
+        // buffer grow (*3). The checks (*1) and (*2) make sure there is
+        // minimum and maximum for the number of removed objects.
+        bool should_gc() const noexcept {
+            if (m_count_removed < 10 * 1000) { // *1
+                return false;
+            }
+            if (m_count_removed >  5 * 1000 * 1000) { // *2
+                return true;
+            }
+            if (m_count_removed * 5 < m_count_items) { // *3
+                return false;
+            }
+            return m_buffer.capacity() - m_buffer.committed() < 10 * 1024; // *4
+        }
+
+    public:
+
+        ItemStash() :
+            m_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes) {
+        }
+
+        /**
+         * Return an estimate of the number of bytes currently used by this
+         * ItemStash instance.
+         *
+         * Complexity: Constant.
+         */
+        std::size_t used_memory() const noexcept {
+            return sizeof(ItemStash) +
+                   m_buffer.capacity() +
+                   m_index.capacity() * sizeof(std::size_t);
+        }
+
+        /**
+         * The number of items currently in the stash. This is the number
+         * added minus the number removed.
+         *
+         * Complexity: Constant.
+         */
+        std::size_t size() const noexcept {
+            return m_count_items;
+        }
+
+        /**
+         * The number of removed items currently still taking up memory in
+         * the stash. You can call garbage_collect() to remove them.
+         *
+         * Complexity: Constant.
+         */
+        std::size_t count_removed() const noexcept {
+            return m_count_removed;
+        }
+
+        /**
+         * Clear all items from the stash. This will not necessarily release
+         * any memory. All handles are invalidated.
+         */
+        void clear() {
+            m_buffer.clear();
+            m_index.clear();
+            m_count_items = 0;
+            m_count_removed = 0;
+        }
+
+        /**
+         * Add an item to the stash. This will invalidate any pointers and
+         * references into the stash, but handles are still valid.
+         *
+         * Complexity: Amortized constant.
+         */
+        handle_type add_item(const osmium::memory::Item& item) {
+            if (should_gc()) {
+                garbage_collect();
+            }
+            ++m_count_items;
+            const auto offset = m_buffer.committed();
+            m_buffer.add_item(item);
+            m_buffer.commit();
+            m_index.push_back(offset);
+            return handle_type{m_index.size()};
+        }
+
+        /**
+         * Get a reference to an item in the stash. Note that this reference
+         * will be invalidated by any add_item() or clear() calls.
+         *
+         * Complexity: Constant.
+         *
+         * @param handle A handle returned by add_item().
+         *
+         * @pre Handle must be a valid handle and referring to a non-removed
+         *      item.
+         */
+        osmium::memory::Item& get_item(handle_type handle) const {
+            return m_buffer.get<osmium::memory::Item>(get_item_offset(handle));
+        }
+
+        /**
+         * Get a reference to an item in the stash. Note that this reference
+         * will be invalidated by any add_item() or clear() calls.
+         *
+         * Complexity: Constant.
+         *
+         * @param handle A handle returned by add_item().
+         * @tparam T Type you want to the data to be interpreted as. You must
+         *         be sure that the item has the specified type, this will
+         *         not be checked!
+         * @returns Reference of given type pointing to the data in the
+         *          stash.
+         * @pre Handle must be a valid handle and referring to a non-removed
+         *      item.
+         */
+        template <typename T>
+        T& get(handle_type handle) const {
+            return static_cast<T&>(get_item(handle));
+        }
+
+        /**
+         * Garbage collect the memory used by the ItemStash. This will free up
+         * memory for adding new items. No memory is actually returned to the
+         * OS. Usually you do not need to call this, because add_item() will
+         * call it for you as necessary.
+         *
+         * Complexity: Linear in size() + count_removed().
+         */
+        void garbage_collect() {
+#ifdef OSMIUM_ITEM_STORAGE_GC_DEBUG
+            std::cerr << "GC items=" << m_count_items << " removed=" << m_count_removed << " buffer.committed=" << m_buffer.committed() << " buffer.capacity=" << m_buffer.capacity() << "\n";
+            using clock = std::chrono::high_resolution_clock;
+            std::chrono::time_point<clock> start = clock::now();
+#endif
+
+            m_count_removed = 0;
+            cleanup_helper helper{m_index};
+            m_buffer.purge_removed(&helper);
+
+#ifdef OSMIUM_ITEM_STORAGE_GC_DEBUG
+            std::chrono::time_point<clock> stop = clock::now();
+            const int64_t time = std::chrono::duration_cast<std::chrono::microseconds>(stop - start).count();
+            m_gc_time += time;
+            std::cerr << "        time=" << time
+                      << "us total=" << m_gc_time << "us\n";
+#endif
+        }
+
+        /**
+         * Remove an item from the stash. The item will be marked as removed
+         * and the handle will be invalidated. No memory will actually be
+         * freed.
+         *
+         * Complexity: Constant.
+         *
+         * @param handle A handle returned by add_item().
+         *
+         * @pre Handle must be a valid handle and referring to a non-removed
+         *      item.
+         */
+        void remove_item(handle_type handle) {
+            auto& offset = get_item_offset_ref(handle);
+            auto& item = m_buffer.get<osmium::memory::Item>(offset);
+            assert(!item.removed() && "can not call remove_item() on already removed item");
+            item.set_removed(true);
+            offset = removed_item_offset;
+            --m_count_items;
+            ++m_count_removed;
+        }
+
+    }; // class ItemStash
+
+} // namespace osmium
+
+#endif // OSMIUM_ITEM_STASH_HPP
diff --git a/include/osmium/thread/function_wrapper.hpp b/include/osmium/thread/function_wrapper.hpp
index eeb9425..ed68d8f 100644
--- a/include/osmium/thread/function_wrapper.hpp
+++ b/include/osmium/thread/function_wrapper.hpp
@@ -79,6 +79,7 @@ namespace osmium {
             // Constructor must not be "explicit" for wrapper
             // to work seemlessly.
             template <typename TFunction>
+            // cppcheck-suppress noExplicitConstructor
             function_wrapper(TFunction&& f) :
                 impl(new impl_type<TFunction>(std::forward<TFunction>(f))) {
             }
@@ -86,7 +87,7 @@ namespace osmium {
             // The integer parameter is only used to signal that we want
             // the special function wrapper that makes the worker thread
             // shut down.
-            function_wrapper(int) :
+            explicit function_wrapper(int) :
                 impl(new impl_base()) {
             }
 
diff --git a/include/osmium/thread/pool.hpp b/include/osmium/thread/pool.hpp
index a89b3e9..5539e7a 100644
--- a/include/osmium/thread/pool.hpp
+++ b/include/osmium/thread/pool.hpp
@@ -76,8 +76,8 @@ namespace osmium {
                 return num_threads;
             }
 
-            inline size_t get_work_queue_size() noexcept {
-                const size_t n = osmium::config::get_max_queue_size("WORK", 10);
+            inline std::size_t get_work_queue_size() noexcept {
+                const std::size_t n = osmium::config::get_max_queue_size("WORK", 10);
                 return n > 2 ? n : 2;
             }
 
@@ -130,6 +130,11 @@ namespace osmium {
                 }
             }
 
+        public:
+
+            static constexpr int default_num_threads = 0;
+            static constexpr int default_queue_size = 0;
+
             /**
              * Create thread pool with the given number of threads. If
              * num_threads is 0, the number of threads is read from
@@ -141,9 +146,12 @@ namespace osmium {
              * given number, ie it will leave a number of cores unused.
              *
              * In all cases the minimum number of threads in the pool is 1.
+             *
+             * If max_queue_size is 0, the queue size is read from
+             * the environment variable OSMIUM_MAX_WORK_QUEUE_SIZE.
              */
-            explicit Pool(int num_threads, size_t max_queue_size) :
-                m_work_queue(max_queue_size, "work"),
+            explicit Pool(int num_threads = default_num_threads, std::size_t max_queue_size = default_queue_size) :
+                m_work_queue(max_queue_size > 0 ? max_queue_size : detail::get_work_queue_size(), "work"),
                 m_threads(),
                 m_joiner(m_threads),
                 m_num_threads(detail::get_pool_size(num_threads, osmium::config::get_pool_threads(), std::thread::hardware_concurrency())) {
@@ -158,12 +166,8 @@ namespace osmium {
                 }
             }
 
-        public:
-
-            static constexpr int default_num_threads = 0;
-
-            static Pool& instance() {
-                static Pool pool(default_num_threads, detail::get_work_queue_size());
+            static Pool& default_instance() {
+                static Pool pool{};
                 return pool;
             }
 
@@ -178,7 +182,11 @@ namespace osmium {
                 shutdown_all_workers();
             }
 
-            size_t queue_size() const {
+            int num_threads() const noexcept {
+                return m_num_threads;
+            }
+
+            std::size_t queue_size() const {
                 return m_work_queue.size();
             }
 
@@ -188,11 +196,10 @@ namespace osmium {
 
             template <typename TFunction>
             std::future<typename std::result_of<TFunction()>::type> submit(TFunction&& func) {
-
                 using result_type = typename std::result_of<TFunction()>::type;
 
-                std::packaged_task<result_type()> task(std::forward<TFunction>(func));
-                std::future<result_type> future_result(task.get_future());
+                std::packaged_task<result_type()> task{std::forward<TFunction>(func)};
+                std::future<result_type> future_result{task.get_future()};
                 m_work_queue.push(std::move(task));
 
                 return future_result;
diff --git a/include/osmium/thread/queue.hpp b/include/osmium/thread/queue.hpp
index 3896fef..39df685 100644
--- a/include/osmium/thread/queue.hpp
+++ b/include/osmium/thread/queue.hpp
@@ -39,7 +39,6 @@ DEALINGS IN THE SOFTWARE.
 #include <mutex>
 #include <queue>
 #include <string>
-#include <thread>
 #include <utility> // IWYU pragma: keep
 
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
@@ -61,7 +60,7 @@ namespace osmium {
 
             /// Maximum size of this queue. If the queue is full pushing to
             /// the queue will block.
-            const size_t m_max_size;
+            const std::size_t m_max_size;
 
             /// Name of this queue (for debugging only).
             const std::string m_name;
@@ -78,7 +77,7 @@ namespace osmium {
 
 #ifdef OSMIUM_DEBUG_QUEUE_SIZE
             /// The largest size the queue has been so far.
-            size_t m_largest_size;
+            std::size_t m_largest_size;
 
             /// The number of times push() was called on the queue.
             std::atomic<int> m_push_counter;
@@ -107,7 +106,7 @@ namespace osmium {
              *                 0 for an unlimited size.
              * @param name Optional name for this queue. (Used for debugging.)
              */
-            explicit Queue(size_t max_size = 0, const std::string& name = "") :
+            explicit Queue(std::size_t max_size = 0, const std::string& name = "") :
                 m_max_size(max_size),
                 m_name(name),
                 m_mutex(),
@@ -216,7 +215,7 @@ namespace osmium {
                 return m_queue.empty();
             }
 
-            size_t size() const {
+            std::size_t size() const {
                 std::lock_guard<std::mutex> lock{m_mutex};
                 return m_queue.size();
             }
diff --git a/include/osmium/util/config.hpp b/include/osmium/util/config.hpp
index 8dba911..f9cc96c 100644
--- a/include/osmium/util/config.hpp
+++ b/include/osmium/util/config.hpp
@@ -33,6 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cassert>
+#include <cstddef>
 #include <cstdlib>
 #include <cstring>
 #include <string>
@@ -66,7 +68,8 @@ namespace osmium {
             return true;
         }
 
-        inline size_t get_max_queue_size(const char* queue_name, size_t default_value) noexcept {
+        inline std::size_t get_max_queue_size(const char* queue_name, std::size_t default_value) noexcept {
+            assert(queue_name);
             std::string name{"OSMIUM_MAX_"};
             name += queue_name;
             name += "_QUEUE_SIZE";
diff --git a/include/osmium/util/file.hpp b/include/osmium/util/file.hpp
index 17897b4..431445b 100644
--- a/include/osmium/util/file.hpp
+++ b/include/osmium/util/file.hpp
@@ -67,7 +67,7 @@ namespace osmium {
          * @returns file size
          * @throws std::system_error If system call failed
          */
-        inline size_t file_size(int fd) {
+        inline std::size_t file_size(int fd) {
 #ifdef _MSC_VER
             // Windows implementation
             // https://msdn.microsoft.com/en-us/library/dfbc2kec.aspx
@@ -75,14 +75,14 @@ namespace osmium {
             if (size == -1L) {
                 throw std::system_error{errno, std::system_category(), "Could not get file size"};
             }
-            return size_t(size);
+            return static_cast<std::size_t>(size);
 #else
             // Unix implementation
             struct stat s;
             if (::fstat(fd, &s) != 0) {
                 throw std::system_error{errno, std::system_category(), "Could not get file size"};
             }
-            return size_t(s.st_size);
+            return static_cast<std::size_t>(s.st_size);
 #endif
         }
 
@@ -94,7 +94,7 @@ namespace osmium {
          * @returns file size
          * @throws std::system_error If system call failed
          */
-        inline size_t file_size(const char* name) {
+        inline std::size_t file_size(const char* name) {
 #ifdef _MSC_VER
             // Windows implementation
             // https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx
@@ -109,7 +109,7 @@ namespace osmium {
                 throw std::system_error{errno, std::system_category(), std::string{"Could not get file size of file '"} + name + "'"};
             }
 #endif
-            return size_t(s.st_size);
+            return static_cast<std::size_t>(s.st_size);
         }
 
         /**
@@ -120,7 +120,7 @@ namespace osmium {
          * @returns file size
          * @throws std::system_error If system call failed
          */
-        inline size_t file_size(const std::string& name) {
+        inline std::size_t file_size(const std::string& name) {
             return file_size(name.c_str());
         }
 
@@ -132,7 +132,7 @@ namespace osmium {
          * @param new_size New size
          * @throws std::system_error If ftruncate(2) call failed
          */
-        inline void resize_file(int fd, size_t new_size) {
+        inline void resize_file(int fd, std::size_t new_size) {
 #ifdef _WIN32
             // https://msdn.microsoft.com/en-us/library/whx354w1.aspx
             if (::_chsize_s(fd, static_cast_with_assert<__int64>(new_size)) != 0) {
@@ -146,7 +146,7 @@ namespace osmium {
         /**
          * Get the page size for this system.
          */
-        inline size_t get_pagesize() {
+        inline std::size_t get_pagesize() {
 #ifdef _WIN32
             // Windows implementation
             SYSTEM_INFO si;
@@ -154,7 +154,7 @@ namespace osmium {
             return si.dwPageSize;
 #else
             // Unix implementation
-            return size_t(::sysconf(_SC_PAGESIZE));
+            return static_cast<std::size_t>(::sysconf(_SC_PAGESIZE));
 #endif
         }
 
@@ -164,17 +164,17 @@ namespace osmium {
          * @param fd Open file descriptor.
          * @returns File offset or 0 if it is not available.
          */
-        inline size_t file_offset(int fd) {
+        inline std::size_t file_offset(int fd) {
 #ifdef _MSC_VER
             // https://msdn.microsoft.com/en-us/library/1yee101t.aspx
-            auto offset = _lseeki64(fd, 0, SEEK_CUR);
+            const auto offset = _lseeki64(fd, 0, SEEK_CUR);
 #else
-            auto offset = ::lseek(fd, 0, SEEK_CUR);
+            const auto offset = ::lseek(fd, 0, SEEK_CUR);
 #endif
             if (offset == -1) {
                 return 0;
             }
-            return size_t(offset);
+            return static_cast<std::size_t>(offset);
         }
 
         /**
diff --git a/include/osmium/util/iterator.hpp b/include/osmium/util/iterator.hpp
index 1cc6e0e..bd8a692 100644
--- a/include/osmium/util/iterator.hpp
+++ b/include/osmium/util/iterator.hpp
@@ -33,7 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstddef>
 #include <type_traits>
 #include <utility>
 
@@ -44,27 +43,19 @@ namespace osmium {
 
         using iterator = It;
 
-        explicit iterator_range(P&& p) :
+        explicit iterator_range(P&& p) noexcept :
             P(std::forward<P>(p)) {
         }
-/*
-        It begin() {
-            return this->first;
-        }
 
-        It end() {
-            return this->second;
-        }
-*/
-        It begin() const {
+        It begin() const noexcept {
             return this->first;
         }
 
-        It end() const {
+        It end() const noexcept {
             return this->second;
         }
 
-        size_t empty() const {
+        bool empty() const noexcept {
             return begin() == end();
         }
 
@@ -74,9 +65,9 @@ namespace osmium {
      * Helper function to create iterator_range from std::pair.
      */
     template <typename P, typename It = typename P::first_type>
-    inline iterator_range<It> make_range(P&& p) {
+    inline iterator_range<It> make_range(P&& p) noexcept {
         static_assert(std::is_same<P, std::pair<It, It>>::value, "make_range needs pair of iterators as argument");
-        return iterator_range<It>(std::forward<P>(p));
+        return iterator_range<It>{std::forward<P>(p)};
     }
 
 } // namespace osmium
diff --git a/include/osmium/util/memory_mapping.hpp b/include/osmium/util/memory_mapping.hpp
index 7504c3f..a26e372 100644
--- a/include/osmium/util/memory_mapping.hpp
+++ b/include/osmium/util/memory_mapping.hpp
@@ -105,7 +105,7 @@ namespace osmium {
         private:
 
             /// The size of the mapping
-            size_t m_size;
+            std::size_t m_size;
 
             /// Offset into the file
             off_t m_offset;
@@ -137,9 +137,9 @@ namespace osmium {
 
             flag_type get_flags() const noexcept;
 
-            static size_t check_size(size_t size) {
+            static std::size_t check_size(std::size_t size) {
                 if (size == 0) {
-                    throw std::runtime_error("Zero-sized mapping is not allowed.");
+                    throw std::runtime_error{"Zero-sized mapping is not allowed."};
                 }
                 return size;
             }
@@ -181,14 +181,14 @@ namespace osmium {
              * @param offset Offset into the file where the mapping should start
              * @throws std::system_error if the mapping fails
              */
-            MemoryMapping(size_t size, mapping_mode mode, int fd=-1, off_t offset=0);
+            MemoryMapping(std::size_t size, mapping_mode mode, int fd=-1, off_t offset=0);
 
             /**
              * @deprecated
              * For backwards compatibility only. Use the constructor taking
              * a mapping_mode as second argument instead.
              */
-            OSMIUM_DEPRECATED MemoryMapping(size_t size, bool writable=true, int fd=-1, off_t offset=0) :
+            OSMIUM_DEPRECATED MemoryMapping(std::size_t size, bool writable=true, int fd=-1, off_t offset=0) :
                 MemoryMapping(size, writable ? mapping_mode::write_shared : mapping_mode::readonly, fd, offset)  {
             }
 
@@ -240,7 +240,7 @@ namespace osmium {
              *
              * @throws std::system_error if the remapping fails.
              */
-            void resize(size_t new_size);
+            void resize(std::size_t new_size);
 
             /**
              * In a boolean context a MemoryMapping is true when it is a valid
@@ -255,7 +255,7 @@ namespace osmium {
              * the mapping with. The actual mapping will probably be larger
              * because the system will round it to the page size.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 return m_size;
             }
 
@@ -285,7 +285,7 @@ namespace osmium {
                 if (is_valid()) {
                     return reinterpret_cast<T*>(m_addr);
                 }
-                throw std::runtime_error("invalid memory mapping");
+                throw std::runtime_error{"invalid memory mapping"};
             }
 
         }; // class MemoryMapping
@@ -304,7 +304,7 @@ namespace osmium {
 
         public:
 
-            explicit AnonymousMemoryMapping(size_t size) :
+            explicit AnonymousMemoryMapping(std::size_t size) :
                 MemoryMapping(size, mapping_mode::write_private) {
             }
 
@@ -313,7 +313,7 @@ namespace osmium {
              * On systems other than Linux anonymous mappings can not be
              * resized!
              */
-            void resize(size_t) = delete;
+            void resize(std::size_t) = delete;
 #endif
 
         }; // class AnonymousMemoryMapping
@@ -340,7 +340,7 @@ namespace osmium {
              * @param size Number of objects of type T to be mapped
              * @throws std::system_error if the mapping fails
              */
-            explicit TypedMemoryMapping(size_t size) :
+            explicit TypedMemoryMapping(std::size_t size) :
                 m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) {
             }
 
@@ -354,7 +354,7 @@ namespace osmium {
              * @param offset Offset into the file where the mapping should start
              * @throws std::system_error if the mapping fails
              */
-            TypedMemoryMapping(size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset = 0) :
+            TypedMemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset = 0) :
                 m_mapping(sizeof(T) * size, mode, fd, sizeof(T) * offset) {
             }
 
@@ -363,8 +363,11 @@ namespace osmium {
              * For backwards compatibility only. Use the constructor taking
              * a mapping_mode as second argument instead.
              */
-            OSMIUM_DEPRECATED TypedMemoryMapping(size_t size, bool writable, int fd, off_t offset = 0) :
-                m_mapping(sizeof(T) * size, writable ? MemoryMapping::mapping_mode::write_shared : MemoryMapping::mapping_mode::readonly, fd, sizeof(T) * offset) {
+            OSMIUM_DEPRECATED TypedMemoryMapping(std::size_t size, bool writable, int fd, off_t offset = 0) :
+                m_mapping(sizeof(T) * size,
+                          writable ? MemoryMapping::mapping_mode::write_shared : MemoryMapping::mapping_mode::readonly,
+                          fd,
+                          sizeof(T) * offset) {
             }
 
             /// You can not copy construct a TypedMemoryMapping.
@@ -410,7 +413,7 @@ namespace osmium {
              * @param new_size Number of objects of type T to resize to
              * @throws std::system_error if the remapping fails
              */
-            void resize(size_t new_size) {
+            void resize(std::size_t new_size) {
                 m_mapping.resize(sizeof(T) * new_size);
             }
 
@@ -427,7 +430,7 @@ namespace osmium {
              * you created the mapping with. The actual mapping will probably
              * be larger because the system will round it to the page size.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 assert(m_mapping.size() % sizeof(T) == 0);
                 return m_mapping.size() / sizeof(T);
             }
@@ -489,7 +492,7 @@ namespace osmium {
 
         public:
 
-            explicit AnonymousTypedMemoryMapping(size_t size) :
+            explicit AnonymousTypedMemoryMapping(std::size_t size) :
                 TypedMemoryMapping<T>(size) {
             }
 
@@ -498,7 +501,7 @@ namespace osmium {
              * On systems other than Linux anonymous mappings can not be
              * resized!
              */
-            void resize(size_t) = delete;
+            void resize(std::size_t) = delete;
 #endif
 
         }; // class AnonymousTypedMemoryMapping
@@ -547,7 +550,7 @@ inline int osmium::util::MemoryMapping::get_flags() const noexcept {
     return MAP_PRIVATE;
 }
 
-inline osmium::util::MemoryMapping::MemoryMapping(size_t size, mapping_mode mode, int fd, off_t offset) :
+inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, mapping_mode mode, int fd, off_t offset) :
     m_size(check_size(size)),
     m_offset(offset),
     m_fd(resize_fd(fd)),
@@ -555,7 +558,7 @@ inline osmium::util::MemoryMapping::MemoryMapping(size_t size, mapping_mode mode
     m_addr(::mmap(nullptr, m_size, get_protection(), get_flags(), m_fd, m_offset)) {
     assert(!(fd == -1 && mode == mapping_mode::readonly));
     if (!is_valid()) {
-        throw std::system_error(errno, std::system_category(), "mmap failed");
+        throw std::system_error{errno, std::system_category(), "mmap failed"};
     }
 }
 
@@ -582,19 +585,19 @@ inline osmium::util::MemoryMapping& osmium::util::MemoryMapping::operator=(osmiu
 inline void osmium::util::MemoryMapping::unmap() {
     if (is_valid()) {
         if (::munmap(m_addr, m_size) != 0) {
-            throw std::system_error(errno, std::system_category(), "munmap failed");
+            throw std::system_error{errno, std::system_category(), "munmap failed"};
         }
         make_invalid();
     }
 }
 
-inline void osmium::util::MemoryMapping::resize(size_t new_size) {
+inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
     assert(new_size > 0 && "can not resize to zero size");
     if (m_fd == -1) { // anonymous mapping
 #ifdef __linux__
         m_addr = ::mremap(m_addr, m_size, new_size, MREMAP_MAYMOVE);
         if (!is_valid()) {
-            throw std::system_error(errno, std::system_category(), "mremap failed");
+            throw std::system_error{errno, std::system_category(), "mremap failed"};
         }
         m_size = new_size;
 #else
@@ -606,7 +609,7 @@ inline void osmium::util::MemoryMapping::resize(size_t new_size) {
         resize_fd(m_fd);
         m_addr = ::mmap(nullptr, new_size, get_protection(), get_flags(), m_fd, m_offset);
         if (!is_valid()) {
-            throw std::system_error(errno, std::system_category(), "mmap (remap) failed");
+            throw std::system_error{errno, std::system_category(), "mmap (remap) failed"};
         }
     }
 }
@@ -673,11 +676,20 @@ inline HANDLE osmium::util::MemoryMapping::create_file_mapping() const noexcept
     if (m_fd != -1) {
         _setmode(m_fd, _O_BINARY);
     }
-    return CreateFileMapping(get_handle(), nullptr, get_protection(), osmium::util::dword_hi(static_cast<uint64_t>(m_size) + m_offset), osmium::util::dword_lo(static_cast<uint64_t>(m_size) + m_offset), nullptr);
+    return CreateFileMapping(get_handle(),
+                             nullptr,
+                             get_protection(),
+                             osmium::util::dword_hi(static_cast<uint64_t>(m_size) + m_offset),
+                             osmium::util::dword_lo(static_cast<uint64_t>(m_size) + m_offset),
+                             nullptr);
 }
 
 inline void* osmium::util::MemoryMapping::map_view_of_file() const noexcept {
-    return MapViewOfFile(m_handle, get_flags(), osmium::util::dword_hi(m_offset), osmium::util::dword_lo(m_offset), m_size);
+    return MapViewOfFile(m_handle,
+                         get_flags(),
+                         osmium::util::dword_hi(m_offset),
+                         osmium::util::dword_lo(m_offset),
+                         m_size);
 }
 
 inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
@@ -688,7 +700,14 @@ inline void osmium::util::MemoryMapping::make_invalid() noexcept {
     m_addr = nullptr;
 }
 
-inline osmium::util::MemoryMapping::MemoryMapping(size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) :
+// GetLastError() returns a DWORD (A 32-bit unsigned integer), but the error
+// code for std::system_error is an int. So we convert this here and hope
+// it all works.
+inline int last_error() noexcept {
+    return static_cast<int>(GetLastError());
+}
+
+inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) :
     m_size(check_size(size)),
     m_offset(offset),
     m_fd(resize_fd(fd)),
@@ -697,12 +716,12 @@ inline osmium::util::MemoryMapping::MemoryMapping(size_t size, MemoryMapping::ma
     m_addr(nullptr) {
 
     if (!m_handle) {
-        throw std::system_error(GetLastError(), std::system_category(), "CreateFileMapping failed");
+        throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
     }
 
     m_addr = map_view_of_file();
     if (!is_valid()) {
-        throw std::system_error(GetLastError(), std::system_category(), "MapViewOfFile failed");
+        throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
     }
 }
 
@@ -732,21 +751,21 @@ inline osmium::util::MemoryMapping& osmium::util::MemoryMapping::operator=(osmiu
 
 inline void osmium::util::MemoryMapping::unmap() {
     if (is_valid()) {
-        if (! UnmapViewOfFile(m_addr)) {
-            throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile failed");
+        if (!UnmapViewOfFile(m_addr)) {
+            throw std::system_error{last_error(), std::system_category(), "UnmapViewOfFile failed"};
         }
         make_invalid();
     }
 
     if (m_handle) {
-        if (! CloseHandle(m_handle)) {
-            throw std::system_error(GetLastError(), std::system_category(), "CloseHandle failed");
+        if (!CloseHandle(m_handle)) {
+            throw std::system_error{last_error(), std::system_category(), "CloseHandle failed"};
         }
         m_handle = nullptr;
     }
 }
 
-inline void osmium::util::MemoryMapping::resize(size_t new_size) {
+inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
     unmap();
 
     m_size = new_size;
@@ -754,12 +773,12 @@ inline void osmium::util::MemoryMapping::resize(size_t new_size) {
 
     m_handle = create_file_mapping();
     if (!m_handle) {
-        throw std::system_error(GetLastError(), std::system_category(), "CreateFileMapping failed");
+        throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
     }
 
     m_addr = map_view_of_file();
     if (!is_valid()) {
-        throw std::system_error(GetLastError(), std::system_category(), "MapViewOfFile failed");
+        throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
     }
 }
 
diff --git a/include/osmium/util/options.hpp b/include/osmium/util/options.hpp
index 7b23652..c39bdbf 100644
--- a/include/osmium/util/options.hpp
+++ b/include/osmium/util/options.hpp
@@ -108,11 +108,11 @@ namespace osmium {
              * be set to "true".
              */
             void set(const std::string& data) {
-                const size_t pos = data.find_first_of('=');
+                const std::size_t pos = data.find_first_of('=');
                 if (pos == std::string::npos) {
                     m_options[data] = "true";
                 } else {
-                    std::string value = data.substr(pos+1);
+                    const std::string value{data.substr(pos+1)};
                     set(data.substr(0, pos), value);
                 }
             }
@@ -121,7 +121,7 @@ namespace osmium {
              * Get value of "key" option. If not set, the default_value (or
              * empty string) is returned.
              */
-            std::string get(const std::string& key, const std::string& default_value="") const noexcept {
+            std::string get(const std::string& key, const std::string& default_value = "") const noexcept {
                 const auto it = m_options.find(key);
                 if (it == m_options.end()) {
                     return default_value;
@@ -134,7 +134,7 @@ namespace osmium {
              * Will return false if the value is unset.
              */
             bool is_true(const std::string& key) const noexcept {
-                const std::string value = get(key);
+                const std::string value{get(key)};
                 return (value == "true" || value == "yes");
             }
 
@@ -143,14 +143,14 @@ namespace osmium {
              * Will return true if the value is unset.
              */
             bool is_not_false(const std::string& key) const noexcept {
-                const std::string value = get(key);
+                const std::string value{get(key)};
                 return !(value == "false" || value == "no");
             }
 
             /**
              * The number of options set.
              */
-            size_t size() const noexcept {
+            std::size_t size() const noexcept {
                 return m_options.size();
             }
 
diff --git a/include/osmium/util/progress_bar.hpp b/include/osmium/util/progress_bar.hpp
index 6e6d012..fed37eb 100644
--- a/include/osmium/util/progress_bar.hpp
+++ b/include/osmium/util/progress_bar.hpp
@@ -33,6 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstddef>
 #include <iostream>
 
 namespace osmium {
@@ -51,23 +52,23 @@ namespace osmium {
             return "                                                                     ";
         }
 
-        static constexpr const size_t length = 70;
+        static constexpr const std::size_t length = 70;
 
         // The max size is the file size if there is a single file and the
         // sum of all file sizes if there are multiple files. It corresponds
         // to 100%.
-        size_t m_max_size;
+        std::size_t m_max_size;
 
         // The sum of the file sizes already done.
-        size_t m_done_size = 0;
+        std::size_t m_done_size = 0;
 
         // The currently read size in the current file.
-        size_t m_current_size = 0;
+        std::size_t m_current_size = 0;
 
         // The percentage calculated when it was last displayed. Used to decide
         // whether we need to update the display. Start setting is one that
         // will always be different from any legal setting.
-        size_t m_prev_percent = 100 + 1;
+        std::size_t m_prev_percent = 100 + 1;
 
         // Is the progress bar enabled at all?
         bool m_enable;
@@ -77,13 +78,13 @@ namespace osmium {
         bool m_do_cleanup = true;
 
         void display() {
-            const size_t percent = 100 * (m_done_size + m_current_size) / m_max_size;
+            const std::size_t percent = 100 * (m_done_size + m_current_size) / m_max_size;
             if (m_prev_percent == percent) {
                 return;
             }
             m_prev_percent = percent;
 
-            const size_t num = size_t(percent * (length / 100.0));
+            const auto num = static_cast<std::size_t>(percent * (length / 100.0));
             std::cerr << '[';
             if (num >= length) {
                 std::cerr << bar();
@@ -109,7 +110,7 @@ namespace osmium {
          * @param enable Set to false to disable (for instance if stderr is
          *               not a TTY).
          */
-        ProgressBar(size_t max_size, bool enable) noexcept :
+        ProgressBar(std::size_t max_size, bool enable) noexcept :
             m_max_size(max_size),
             m_enable(max_size > 0 && enable) {
         }
@@ -133,7 +134,7 @@ namespace osmium {
          * @param current_size Current size. Used together with the max_size
          *                     from constructor to calculate the percentage.
          */
-        void update(size_t current_size) {
+        void update(std::size_t current_size) {
             if (!m_enable) {
                 return;
             }
@@ -149,7 +150,7 @@ namespace osmium {
          *
          * @param file_size The size of the file just finished.
          */
-        void file_done(size_t file_size) {
+        void file_done(std::size_t file_size) {
             if (m_enable) {
                 m_done_size += file_size;
                 m_current_size = 0;
diff --git a/include/osmium/util/string.hpp b/include/osmium/util/string.hpp
index 595307e..6eef05f 100644
--- a/include/osmium/util/string.hpp
+++ b/include/osmium/util/string.hpp
@@ -44,15 +44,15 @@ namespace osmium {
      *
      * @param str The string to be split.
      * @param sep The separator character.
-     * @param compact Set this to true to remove empty strings from result
+     * @param compact Set this to true to remove empty strings from result.
      * @returns Vector with the parts of the string split up.
      */
     inline std::vector<std::string> split_string(const std::string& str, const char sep, bool compact = false) {
         std::vector<std::string> tokens;
 
         if (!str.empty()) {
-            size_t pos = 0;
-            size_t nextpos = str.find_first_of(sep);
+            std::size_t pos = 0;
+            std::size_t nextpos = str.find_first_of(sep);
             while (nextpos != std::string::npos) {
                 if (!compact || (nextpos - pos != 0)) {
                     tokens.push_back(str.substr(pos, nextpos-pos));
@@ -69,19 +69,19 @@ namespace osmium {
     }
 
     /**
-     * Split string on the separator character(s).
+     * Split string on any of the separator characters.
      *
      * @param str The string to be split.
      * @param sep The separator character(s).
-     * @param compact Set this to true to remove empty strings from result
+     * @param compact Set this to true to remove empty strings from result.
      * @returns Vector with the parts of the string split up.
      */
     inline std::vector<std::string> split_string(const std::string& str, const char* sep, bool compact = false) {
         std::vector<std::string> tokens;
 
         if (!str.empty()) {
-            size_t pos = 0;
-            size_t nextpos = str.find_first_of(sep);
+            std::size_t pos = 0;
+            std::size_t nextpos = str.find_first_of(sep);
             while (nextpos != std::string::npos) {
                 if (!compact || (nextpos - pos != 0)) {
                     tokens.push_back(str.substr(pos, nextpos-pos));
diff --git a/include/osmium/util/verbose_output.hpp b/include/osmium/util/verbose_output.hpp
index a47d29a..e6f6654 100644
--- a/include/osmium/util/verbose_output.hpp
+++ b/include/osmium/util/verbose_output.hpp
@@ -88,7 +88,7 @@ namespace osmium {
         public:
 
             explicit VerboseOutput(bool verbose = false) noexcept :
-                m_start(time(NULL)),
+                m_start(time(nullptr)),
                 m_verbose(verbose),
                 m_newline(true) {
             }
@@ -101,7 +101,7 @@ namespace osmium {
             VerboseOutput& operator=(VerboseOutput&&) = default;
 
             time_t runtime() const noexcept {
-                return time(NULL) - m_start;
+                return time(nullptr) - m_start;
             }
 
             /// Get "verbose" setting.
diff --git a/include/osmium/version.hpp b/include/osmium/version.hpp
index 5f3b10e..eb1aa36 100644
--- a/include/osmium/version.hpp
+++ b/include/osmium/version.hpp
@@ -34,9 +34,9 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #define LIBOSMIUM_VERSION_MAJOR 2
-#define LIBOSMIUM_VERSION_MINOR 12
-#define LIBOSMIUM_VERSION_PATCH 2
+#define LIBOSMIUM_VERSION_MINOR 13
+#define LIBOSMIUM_VERSION_PATCH 0
 
-#define LIBOSMIUM_VERSION_STRING "2.12.2"
+#define LIBOSMIUM_VERSION_STRING "2.13.0"
 
 #endif // OSMIUM_VERSION_HPP
diff --git a/include/protozero/pbf_builder.hpp b/include/protozero/pbf_builder.hpp
index c40727c..8197395 100644
--- a/include/protozero/pbf_builder.hpp
+++ b/include/protozero/pbf_builder.hpp
@@ -46,7 +46,7 @@ public:
 
     using enum_type = T;
 
-    pbf_builder(std::string& data) noexcept :
+    explicit pbf_builder(std::string& data) noexcept :
         pbf_writer(data) {
     }
 
@@ -83,10 +83,18 @@ public:
         pbf_writer::add_bytes(pbf_tag_type(tag), value, size);
     }
 
+    void add_bytes(T tag, const data_view& value) {
+        pbf_writer::add_bytes(pbf_tag_type(tag), value);
+    }
+
     void add_bytes(T tag, const std::string& value) {
         pbf_writer::add_bytes(pbf_tag_type(tag), value);
     }
 
+    void add_bytes(T tag, const char* value) {
+        pbf_writer::add_bytes(pbf_tag_type(tag), value);
+    }
+
     template <typename... Ts>
     void add_bytes_vectored(T tag, Ts&&... values) {
         pbf_writer::add_bytes_vectored(pbf_tag_type(tag), std::forward<Ts>(values)...);
@@ -96,6 +104,10 @@ public:
         pbf_writer::add_string(pbf_tag_type(tag), value, size);
     }
 
+    void add_string(T tag, const data_view& value) {
+        pbf_writer::add_string(pbf_tag_type(tag), value);
+    }
+
     void add_string(T tag, const std::string& value) {
         pbf_writer::add_string(pbf_tag_type(tag), value);
     }
@@ -108,6 +120,10 @@ public:
         pbf_writer::add_message(pbf_tag_type(tag), value, size);
     }
 
+    void add_message(T tag, const data_view& value) {
+        pbf_writer::add_message(pbf_tag_type(tag), value);
+    }
+
     void add_message(T tag, const std::string& value) {
         pbf_writer::add_message(pbf_tag_type(tag), value);
     }
diff --git a/include/protozero/pbf_message.hpp b/include/protozero/pbf_message.hpp
index f66604a..c599cf1 100644
--- a/include/protozero/pbf_message.hpp
+++ b/include/protozero/pbf_message.hpp
@@ -83,6 +83,10 @@ public:
         return pbf_reader::next(pbf_tag_type(next_tag));
     }
 
+    bool next(T next_tag, pbf_wire_type type) {
+        return pbf_reader::next(pbf_tag_type(next_tag), type);
+    }
+
     T tag() const noexcept {
         return T(pbf_reader::tag());
     }
diff --git a/include/protozero/pbf_reader.hpp b/include/protozero/pbf_reader.hpp
index 98920fa..905ca0a 100644
--- a/include/protozero/pbf_reader.hpp
+++ b/include/protozero/pbf_reader.hpp
@@ -179,7 +179,7 @@ public:
      *
      * @post There is no current field.
      */
-    pbf_reader(const std::pair<const char*, std::size_t>& data) noexcept
+    explicit pbf_reader(const 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),
@@ -196,7 +196,7 @@ public:
      *
      * @post There is no current field.
      */
-    pbf_reader(const std::string& data) noexcept
+    explicit pbf_reader(const std::string& data) noexcept
         : m_data(data.data()),
           m_end(data.data() + data.size()),
           m_wire_type(pbf_wire_type::unknown),
@@ -367,9 +367,9 @@ public:
      * @pre There must be no current field.
      * @post If it returns `true` there is a current field now with the given tag.
      */
-    bool next(pbf_tag_type next_tag, pbf_wire_type wire_type) {
+    bool next(pbf_tag_type next_tag, pbf_wire_type type) {
         while (next()) {
-            if (m_tag == next_tag && m_wire_type == wire_type) {
+            if (m_tag == next_tag && m_wire_type == type) {
                 return true;
             } else {
                 skip();
diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp
index 39dd795..af626bd 100644
--- a/include/protozero/pbf_writer.hpp
+++ b/include/protozero/pbf_writer.hpp
@@ -502,6 +502,17 @@ public:
     }
 
     /**
+     * Add "bytes" field to data. Bytes from the value are written until
+     * a null byte is encountered. The null byte is not added.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Pointer to zero-delimited value to be written
+     */
+    void add_bytes(pbf_tag_type tag, const char* value) {
+        add_bytes(tag, value, std::strlen(value));
+    }
+
+    /**
      * Add "bytes" field to data using vectored input. All the data in the
      * 2nd and further arguments is "concatenated" with only a single copy
      * into the final buffer.
diff --git a/include/protozero/types.hpp b/include/protozero/types.hpp
index 5e14972..3dbdaf1 100644
--- a/include/protozero/types.hpp
+++ b/include/protozero/types.hpp
@@ -16,6 +16,7 @@ documentation.
  * @brief Contains the declaration of low-level types used in the pbf format.
  */
 
+#include <algorithm>
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
@@ -138,6 +139,11 @@ public:
         return m_size;
     }
 
+    /// Returns true if size is 0.
+    constexpr bool empty() const noexcept {
+        return m_size == 0;
+    }
+
     /**
      * Convert data view to string.
      *
@@ -178,7 +184,7 @@ inline void swap(data_view& lhs, data_view& rhs) noexcept {
  * @param rhs Second object.
  */
 inline bool operator==(const data_view& lhs, const data_view& rhs) noexcept {
-    return lhs.size() == rhs.size() && !std::strcmp(lhs.data(), rhs.data());
+    return lhs.size() == rhs.size() && std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data());
 }
 
 /**
diff --git a/include/protozero/version.hpp b/include/protozero/version.hpp
index 9103bdc..6d82823 100644
--- a/include/protozero/version.hpp
+++ b/include/protozero/version.hpp
@@ -23,12 +23,12 @@ documentation.
 #define PROTOZERO_VERSION_MINOR 5
 
 /// The patch number
-#define PROTOZERO_VERSION_PATCH 1
+#define PROTOZERO_VERSION_PATCH 2
 
 /// The complete version number
 #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH)
 
 /// Version number as string
-#define PROTOZERO_VERSION_STRING "1.5.1"
+#define PROTOZERO_VERSION_STRING "1.5.2"
 
 #endif // PROTOZERO_VERSION_HPP
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 767972e..b23c39a 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -142,6 +142,8 @@ add_unit_test(osm test_way)
 add_unit_test(memory test_buffer_basics)
 add_unit_test(memory test_buffer_node)
 add_unit_test(memory test_buffer_purge)
+add_unit_test(memory test_callback_buffer)
+add_unit_test(memory test_item)
 add_unit_test(memory test_type_is_compatible)
 
 add_unit_test(builder test_attr)
@@ -161,6 +163,9 @@ add_unit_test(geom test_tile)
 add_unit_test(geom test_wkb)
 add_unit_test(geom test_wkt)
 
+add_unit_test(handler test_check_order_handler)
+add_unit_test(handler test_dynamic_handler)
+
 add_unit_test(index test_id_set)
 add_unit_test(index test_id_to_location ENABLE_IF ${SPARSEHASH_FOUND})
 add_unit_test(index test_file_based_index)
@@ -182,6 +187,13 @@ add_unit_test(io test_writer ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRAR
 add_unit_test(io test_writer_with_mock_compression ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRARIES})
 add_unit_test(io test_writer_with_mock_encoder ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRARIES})
 
+add_unit_test(relations test_members_database)
+add_unit_test(relations test_read_relations ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRARIES})
+add_unit_test(relations test_relations_database)
+add_unit_test(relations test_relations_manager ENABLE_IF ${Threads_FOUND} LIBS ${OSMIUM_XML_LIBRARIES})
+
+add_unit_test(storage test_item_stash)
+
 add_unit_test(tags test_filter)
 add_unit_test(tags test_operators)
 add_unit_test(tags test_tag_list)
@@ -189,8 +201,10 @@ add_unit_test(tags test_tag_matcher)
 add_unit_test(tags test_tags_filter)
 
 add_unit_test(thread test_pool ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT})
+add_unit_test(thread test_util ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT})
 
 add_unit_test(util test_cast_with_assert)
+add_unit_test(util test_config)
 add_unit_test(util test_delta)
 add_unit_test(util test_double)
 add_unit_test(util test_file)
@@ -200,6 +214,8 @@ add_unit_test(util test_minmax)
 add_unit_test(util test_options)
 add_unit_test(util test_string)
 add_unit_test(util test_string_matcher)
+add_unit_test(util test_timer_disabled)
+add_unit_test(util test_timer_enabled)
 
 
 #-----------------------------------------------------------------------------
diff --git a/test/data-tests/testdata-multipolygon.cpp b/test/data-tests/testdata-multipolygon.cpp
index 6d0328c..06385fb 100644
--- a/test/data-tests/testdata-multipolygon.cpp
+++ b/test/data-tests/testdata-multipolygon.cpp
@@ -9,7 +9,7 @@
 
 #include <osmium/index/map/sparse_mem_array.hpp>
 
-#include <osmium/area/assembler.hpp>
+#include <osmium/area/assembler_legacy.hpp>
 #include <osmium/area/multipolygon_collector.hpp>
 #include <osmium/area/problem_reporter_ogr.hpp>
 #include <osmium/geom/ogr.hpp>
@@ -156,12 +156,12 @@ int main(int argc, char* argv[]) {
     gdalcpp::Dataset dataset{output_format, output_filename, gdalcpp::SRS{}, {"SPATIALITE=TRUE"}};
 
     osmium::area::ProblemReporterOGR problem_reporter{dataset};
-    osmium::area::Assembler::config_type assembler_config;
+    osmium::area::AssemblerLegacy::config_type assembler_config;
     assembler_config.problem_reporter = &problem_reporter;
     assembler_config.check_roles = true;
     assembler_config.create_empty_areas = true;
     assembler_config.debug_level = 2;
-    osmium::area::MultipolygonCollector<osmium::area::Assembler> collector{assembler_config};
+    osmium::area::MultipolygonCollector<osmium::area::AssemblerLegacy> collector{assembler_config};
 
     std::cerr << "Pass 1...\n";
     osmium::io::Reader reader1{input_filename};
diff --git a/test/data-tests/testdata-xml.cpp b/test/data-tests/testdata-xml.cpp
index 5f11429..0808d3f 100644
--- a/test/data-tests/testdata-xml.cpp
+++ b/test/data-tests/testdata-xml.cpp
@@ -73,8 +73,9 @@ static std::string read_gz_file(const char* test_id, const char* suffix) {
     return input;
 }
 
-
+// cppcheck-suppress passedByValue
 static header_buffer_type parse_xml(std::string input) {
+    osmium::thread::Pool pool;
     osmium::io::detail::future_string_queue_type input_queue;
     osmium::io::detail::future_buffer_queue_type output_queue;
     std::promise<osmium::io::Header> header_promise;
@@ -84,6 +85,7 @@ static header_buffer_type parse_xml(std::string input) {
     osmium::io::detail::add_to_queue(input_queue, std::string{});
 
     osmium::io::detail::parser_arguments args = {
+        pool,
         input_queue,
         output_queue,
         header_promise,
diff --git a/test/examples/t/amenity_list/CMakeLists.txt b/test/examples/t/amenity_list/CMakeLists.txt
index c12fc7d..3a99a8a 100644
--- a/test/examples/t/amenity_list/CMakeLists.txt
+++ b/test/examples/t/amenity_list/CMakeLists.txt
@@ -1,7 +1,13 @@
 
-add_test(NAME examples_amenity_list
+add_test(NAME examples_amenity_list_node
          COMMAND osmium_amenity_list ${CMAKE_CURRENT_SOURCE_DIR}/node.osm)
 
-set_tests_properties(examples_amenity_list PROPERTIES
+set_tests_properties(examples_amenity_list_node PROPERTIES
                      PASS_REGULAR_EXPRESSION "  8\\.8721, 53\\.0966 post_office")
 
+add_test(NAME examples_amenity_list_area
+         COMMAND osmium_amenity_list ${CMAKE_CURRENT_SOURCE_DIR}/area.osm)
+
+set_tests_properties(examples_amenity_list_area PROPERTIES
+                     PASS_REGULAR_EXPRESSION "  8\\.5839, 53\\.5602 restaurant")
+
diff --git a/test/examples/t/amenity_list/area.osm b/test/examples/t/amenity_list/area.osm
new file mode 100644
index 0000000..bbf60fb
--- /dev/null
+++ b/test/examples/t/amenity_list/area.osm
@@ -0,0 +1,18 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6">
+  <node id="2791512283" visible="true" version="1" changeset="21695983" timestamp="2014-04-14T20:45:22Z" user="Gerd Taddicken" uid="25464" lat="53.5601034" lon="8.5840408"/>
+  <node id="2791512284" visible="true" version="1" changeset="21695983" timestamp="2014-04-14T20:45:22Z" user="Gerd Taddicken" uid="25464" lat="53.5601127" lon="8.5837753"/>
+  <node id="2791512287" visible="true" version="1" changeset="21695983" timestamp="2014-04-14T20:45:22Z" user="Gerd Taddicken" uid="25464" lat="53.5602544" lon="8.5837933"/>
+  <node id="2791512289" visible="true" version="1" changeset="21695983" timestamp="2014-04-14T20:45:22Z" user="Gerd Taddicken" uid="25464" lat="53.5602464" lon="8.5840476"/>
+  <way id="274425514" visible="true" version="4" changeset="41519398" timestamp="2016-08-17T19:16:40Z" user="Lutalica_1974" uid="2096672">
+    <nd ref="2791512283"/>
+    <nd ref="2791512284"/>
+    <nd ref="2791512287"/>
+    <nd ref="2791512289"/>
+    <nd ref="2791512283"/>
+    <tag k="amenity" v="restaurant"/>
+    <tag k="building" v="house"/>
+    <tag k="cuisine" v="american"/>
+    <tag k="name" v="Seamen's Club"/>
+  </way>
+</osm>
diff --git a/test/examples/t/area_test/CMakeLists.txt b/test/examples/t/area_test/CMakeLists.txt
new file mode 100644
index 0000000..81899a1
--- /dev/null
+++ b/test/examples/t/area_test/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+add_test(NAME examples_area_test_help
+         COMMAND osmium_area_test -h)
+
+set_tests_properties(examples_area_test_help PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^osmium_area_test .* OSMFILE")
+
+add_test(NAME examples_area_test_data
+         COMMAND osmium_area_test ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_area_test_data PROPERTIES
+                     PASS_REGULAR_EXPRESSION "\nWarning! Some member ways missing for these multipolygon relations: 701901\n$")
+
+add_test(NAME examples_area_test_dump
+         COMMAND osmium_area_test -o ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_area_test_dump PROPERTIES
+                     PASS_REGULAR_EXPRESSION "  id=1403801")
+
+add_test(NAME examples_area_test_wkt
+         COMMAND osmium_area_test -w ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_area_test_wkt PROPERTIES
+                     PASS_REGULAR_EXPRESSION "MULTIPOLYGON\\(\\(\\(7.11 1.01,7.14 1.01,7.14 1.04,7.11 1.04,7.11 1.01\\)\\)\\)\n")
+
diff --git a/test/examples/t/area_test/data.osm b/test/examples/t/area_test/data.osm
new file mode 100644
index 0000000..cbdeeaa
--- /dev/null
+++ b/test/examples/t/area_test/data.osm
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="701000" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.01"/>
+    <node id="701001" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.04"/>
+    <node id="701002" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.04"/>
+    <node id="701003" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.01"/>
+    <way id="701800" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701000"/>
+        <nd ref="701001"/>
+        <nd ref="701002"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <way id="701801" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701002"/>
+        <nd ref="701003"/>
+        <nd ref="701000"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <relation id="701900" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701800" role="outer"/>
+        <member type="way" ref="701801" role="outer"/>
+        <tag k="type" v="multipolygon"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+    <relation id="701901" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701802" role="outer"/> <!-- missing member -->
+        <tag k="type" v="multipolygon"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+</osm>
diff --git a/test/examples/t/change_tags/CMakeLists.txt b/test/examples/t/change_tags/CMakeLists.txt
new file mode 100644
index 0000000..b054aa9
--- /dev/null
+++ b/test/examples/t/change_tags/CMakeLists.txt
@@ -0,0 +1,9 @@
+
+add_test(NAME examples_change_tags
+         COMMAND osmium_change_tags ${CMAKE_CURRENT_SOURCE_DIR}/data.osm ${CMAKE_CURRENT_BINARY_DIR}/result.osm)
+
+add_test(NAME examples_change_tags_compare
+         COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_CURRENT_SOURCE_DIR}/result.osm ${CMAKE_CURRENT_BINARY_DIR}/result.osm)
+
+set_tests_properties(examples_change_tags_compare PROPERTIES DEPENDS example_change_tags)
+
diff --git a/test/examples/t/change_tags/data.osm b/test/examples/t/change_tags/data.osm
new file mode 100644
index 0000000..d65c716
--- /dev/null
+++ b/test/examples/t/change_tags/data.osm
@@ -0,0 +1,20 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+  <node id="10" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.01" lon="7.11">
+    <tag k="created_by" v="some editor"/>
+  </node>
+  <node id="11" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.04" lon="7.11"/>
+  <node id="12" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.04" lon="7.14"/>
+  <way id="20" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10"/>
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <tag k="created_by" v="some editor"/>
+    <tag k="landuse" v="forest"/>
+    <tag k="name" v="Example Forest"/>
+  </way>
+  <relation id="30" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+    <tag k="created_by" v="some editor"/>
+    <tag k="note" v=" relation without members"/>
+  </relation>
+</osm>
diff --git a/test/examples/t/change_tags/result.osm b/test/examples/t/change_tags/result.osm
new file mode 100644
index 0000000..1898b89
--- /dev/null
+++ b/test/examples/t/change_tags/result.osm
@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="osmium_change_tags">
+  <node id="10" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.01" lon="7.11"/>
+  <node id="11" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.04" lon="7.11"/>
+  <node id="12" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.04" lon="7.14"/>
+  <way id="20" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10"/>
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <tag k="natural" v="wood"/>
+    <tag k="name" v="Example Forest"/>
+  </way>
+  <relation id="30" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+    <tag k="note" v=" relation without members"/>
+  </relation>
+</osm>
diff --git a/test/examples/t/convert/CMakeLists.txt b/test/examples/t/convert/CMakeLists.txt
new file mode 100644
index 0000000..a5f06fa
--- /dev/null
+++ b/test/examples/t/convert/CMakeLists.txt
@@ -0,0 +1,46 @@
+
+add_test(NAME examples_convert_help
+         COMMAND osmium_convert -h)
+
+set_tests_properties(examples_convert_help PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^osmium_convert .*OUTFILE")
+
+
+add_test(NAME examples_convert_xml_debug
+         COMMAND osmium_convert -t debug ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_convert_xml_debug PROPERTIES
+                     PASS_REGULAR_EXPRESSION "node 701000\n  version:   1 visible")
+
+
+add_test(NAME examples_convert_xml_opl
+         COMMAND osmium_convert -f osm -t opl ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_convert_xml_opl PROPERTIES
+                     PASS_REGULAR_EXPRESSION "n701001 v1 dV c1 t2014-01-01T00:00:00Z i1 utest T x7.11 y1.04")
+
+
+add_test(NAME examples_convert_xml_pbf
+         COMMAND osmium_convert -t pbf ${CMAKE_CURRENT_SOURCE_DIR}/data.osm -)
+
+
+add_test(NAME examples_convert_xml_xml
+         COMMAND osmium_convert -t xml ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_convert_xml_xml PROPERTIES
+                     PASS_REGULAR_EXPRESSION "<node id=\"701001\" ")
+
+# Should give a warning when converting from history to non-history file
+add_test(NAME examples_convert_osh_xml
+         COMMAND osmium_convert -f osh -t osm ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_convert_osh_xml PROPERTIES
+                     PASS_REGULAR_EXPRESSION "Warning! You are converting from an OSM file")
+
+# Should fail when an unknown command line option is used
+add_test(NAME examples_convert_unknown_option
+         COMMAND osmium_convert --unknown)
+
+set_tests_properties(examples_convert_unknown_option PROPERTIES
+                     WILL_FAIL true)
+
diff --git a/test/examples/t/convert/data.osm b/test/examples/t/convert/data.osm
new file mode 100644
index 0000000..cbdeeaa
--- /dev/null
+++ b/test/examples/t/convert/data.osm
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="701000" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.01"/>
+    <node id="701001" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.04"/>
+    <node id="701002" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.04"/>
+    <node id="701003" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.01"/>
+    <way id="701800" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701000"/>
+        <nd ref="701001"/>
+        <nd ref="701002"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <way id="701801" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701002"/>
+        <nd ref="701003"/>
+        <nd ref="701000"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <relation id="701900" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701800" role="outer"/>
+        <member type="way" ref="701801" role="outer"/>
+        <tag k="type" v="multipolygon"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+    <relation id="701901" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701802" role="outer"/> <!-- missing member -->
+        <tag k="type" v="multipolygon"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+</osm>
diff --git a/test/examples/t/count/CMakeLists.txt b/test/examples/t/count/CMakeLists.txt
new file mode 100644
index 0000000..07ac6f4
--- /dev/null
+++ b/test/examples/t/count/CMakeLists.txt
@@ -0,0 +1,8 @@
+
+add_test(NAME examples_count
+         COMMAND osmium_count ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_count PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Nodes: 2\nWays: 1\nRelations: 1\n\nMemory used:"
+)
+
diff --git a/test/examples/t/count/data.osm b/test/examples/t/count/data.osm
new file mode 100644
index 0000000..2a62ece
--- /dev/null
+++ b/test/examples/t/count/data.osm
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6">
+ <node id="1" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1" lat="51.0271601" lon="13.7252197"/>
+ <node id="2" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1" lat="51.0288568" lon="13.7248159"/>
+ <way id="10" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1">
+  <nd ref="1"/>
+  <nd ref="2"/>
+ </way>
+ <relation id="20" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1">
+  <member type="way" ref="10" role=""/>
+ </relation>
+</osm>
diff --git a/test/examples/t/create_pois/CMakeLists.txt b/test/examples/t/create_pois/CMakeLists.txt
new file mode 100644
index 0000000..a2aa7cb
--- /dev/null
+++ b/test/examples/t/create_pois/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+add_test(NAME examples_create_pois_okay
+         COMMAND osmium_create_pois -)
+
+set_tests_properties(examples_create_pois_okay PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^n-1 v1 dV c0 t....-..-..T..:..:..Z i0 u Tamenity=post_box x1\\.23 y3\\.45\nn-2 v1 dV c0 t....-..-..T..:..:..Z i0 u Tamenity=restaurant,name=Chez%20%OSM x1\\.24 y3\\.46\n$"
+)
+
+add_test(NAME examples_create_pois_unknown_file_type
+         COMMAND osmium_create_pois foo)
+
+set_tests_properties(examples_create_pois_unknown_file_type PROPERTIES WILL_FAIL true)
+
diff --git a/test/examples/t/debug/CMakeLists.txt b/test/examples/t/debug/CMakeLists.txt
new file mode 100644
index 0000000..6e2462c
--- /dev/null
+++ b/test/examples/t/debug/CMakeLists.txt
@@ -0,0 +1,55 @@
+
+add_test(NAME examples_debug_all
+         COMMAND osmium_debug ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
+set_tests_properties(examples_debug_all PROPERTIES
+                     PASS_REGULAR_EXPRESSION "  id=1\n.*  id=2\n.*  id=10\n.*  id=20\n"
+)
+
+
+add_test(NAME examples_debug_nodes
+         COMMAND osmium_debug ${CMAKE_CURRENT_SOURCE_DIR}/data.osm n)
+
+set_tests_properties(examples_debug_nodes PROPERTIES
+                     PASS_REGULAR_EXPRESSION "id=1\n.*  id=2\n"
+)
+
+set_tests_properties(examples_debug_nodes PROPERTIES
+                     FAIL_REGULAR_EXPRESSION "id=10\n"
+)
+
+
+add_test(NAME examples_debug_ways
+         COMMAND osmium_debug ${CMAKE_CURRENT_SOURCE_DIR}/data.osm w)
+
+set_tests_properties(examples_debug_ways PROPERTIES
+                     PASS_REGULAR_EXPRESSION "  id=10\n"
+)
+
+set_tests_properties(examples_debug_ways PROPERTIES
+                     FAIL_REGULAR_EXPRESSION "id=20\n"
+)
+
+
+add_test(NAME examples_debug_relations
+         COMMAND osmium_debug ${CMAKE_CURRENT_SOURCE_DIR}/data.osm r)
+
+set_tests_properties(examples_debug_relations PROPERTIES
+                     PASS_REGULAR_EXPRESSION "  id=20\n"
+)
+
+set_tests_properties(examples_debug_relations PROPERTIES
+                     FAIL_REGULAR_EXPRESSION "id=10\n"
+)
+
+add_test(NAME examples_debug_changesets
+         COMMAND osmium_debug ${CMAKE_CURRENT_SOURCE_DIR}/changesets.osm c)
+
+set_tests_properties(examples_debug_changesets PROPERTIES
+                     PASS_REGULAR_EXPRESSION "  id=15449962\n"
+)
+
+set_tests_properties(examples_debug_changesets PROPERTIES
+                     FAIL_REGULAR_EXPRESSION "id=10\n"
+)
+
diff --git a/test/examples/t/debug/changesets.osm b/test/examples/t/debug/changesets.osm
new file mode 100644
index 0000000..61ee023
--- /dev/null
+++ b/test/examples/t/debug/changesets.osm
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="Osmosis 0.41">
+  <changeset id="15449962" created_at="2013-03-22T02:10:17Z" num_changes="140" closed_at="2013-03-22T02:10:24Z" open="false" min_lon="106.8146927" min_lat="-6.1207748" max_lon="106.8212512" max_lat="-6.1133246" user="Vivianecahen" uid="1241761">
+    <tag k="comment" v="Adding Initial buildings and roads"/>
+    <tag k="created_by" v="JOSM/1.5 (5697 en)"/>
+  </changeset>
+</osm>
diff --git a/test/examples/t/debug/data.osm b/test/examples/t/debug/data.osm
new file mode 100644
index 0000000..15fbda3
--- /dev/null
+++ b/test/examples/t/debug/data.osm
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6">
+ <bounds minlon="13.7248159" minlat="51.0271601" maxlon="13.7252197" maxlat="51.0288568"/>
+ <node id="1" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1" lat="51.0271601" lon="13.7252197"/>
+ <node id="2" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1" lat="51.0288568" lon="13.7248159"/>
+ <way id="10" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1">
+  <nd ref="1"/>
+  <nd ref="2"/>
+ </way>
+ <relation id="20" version="1" changeset="1" timestamp="2000-01-01T10:11:12Z" user="test" uid="1">
+  <member type="way" ref="10" role=""/>
+ </relation>
+</osm>
diff --git a/test/examples/t/dump_internal/CMakeLists.txt b/test/examples/t/dump_internal/CMakeLists.txt
new file mode 100644
index 0000000..6938563
--- /dev/null
+++ b/test/examples/t/dump_internal/CMakeLists.txt
@@ -0,0 +1,41 @@
+
+add_test(NAME examples_dump_internal
+         COMMAND osmium_dump_internal ${CMAKE_CURRENT_SOURCE_DIR}/data.osm ${CMAKE_CURRENT_BINARY_DIR}/out)
+
+# XXX Disable failing tests on Windows until we figure out what's wrong
+# See https://github.com/osmcode/libosmium/issues/220
+if(NOT MSVC)
+
+    add_test(NAME examples_dump_internal_index_nodes
+            COMMAND osmium_index_lookup --list=${CMAKE_CURRENT_BINARY_DIR}/out/nodes.idx --type=offset --dump)
+
+    set_tests_properties(examples_dump_internal_index_nodes PROPERTIES
+                        DEPENDS examples_dump_internal
+                        PASS_REGULAR_EXPRESSION "^701000 .*\n701001 .*\n")
+
+
+    add_test(NAME examples_dump_internal_index_ways
+            COMMAND osmium_index_lookup --list=${CMAKE_CURRENT_BINARY_DIR}/out/ways.idx --type=offset --dump)
+
+    set_tests_properties(examples_dump_internal_index_ways PROPERTIES
+                        DEPENDS examples_dump_internal
+                        PASS_REGULAR_EXPRESSION "^701800 .*\n701801 .*\n")
+
+
+    add_test(NAME examples_dump_internal_map_node2way_dump
+            COMMAND osmium_index_lookup --list=${CMAKE_CURRENT_BINARY_DIR}/out/node2way.map --type=id --dump)
+
+    set_tests_properties(examples_dump_internal_map_node2way_dump PROPERTIES
+                        DEPENDS examples_dump_internal
+                        PASS_REGULAR_EXPRESSION "^701000 701800\n701000 701801\n701001 701800\n")
+
+
+    add_test(NAME examples_dump_internal_map_node2way_search
+            COMMAND osmium_index_lookup --list=${CMAKE_CURRENT_BINARY_DIR}/out/node2way.map --type=id --search=701002)
+
+    set_tests_properties(examples_dump_internal_map_node2way_search PROPERTIES
+                        DEPENDS examples_dump_internal
+                        PASS_REGULAR_EXPRESSION "^701002 701800\n701002 701801\n$")
+
+endif()
+
diff --git a/test/examples/t/dump_internal/data.osm b/test/examples/t/dump_internal/data.osm
new file mode 100644
index 0000000..cbdeeaa
--- /dev/null
+++ b/test/examples/t/dump_internal/data.osm
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="701000" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.01"/>
+    <node id="701001" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.04"/>
+    <node id="701002" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.04"/>
+    <node id="701003" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.01"/>
+    <way id="701800" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701000"/>
+        <nd ref="701001"/>
+        <nd ref="701002"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <way id="701801" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701002"/>
+        <nd ref="701003"/>
+        <nd ref="701000"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <relation id="701900" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701800" role="outer"/>
+        <member type="way" ref="701801" role="outer"/>
+        <tag k="type" v="multipolygon"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+    <relation id="701901" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701802" role="outer"/> <!-- missing member -->
+        <tag k="type" v="multipolygon"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+</osm>
diff --git a/test/examples/t/filter_discussions/CMakeLists.txt b/test/examples/t/filter_discussions/CMakeLists.txt
new file mode 100644
index 0000000..21e8f72
--- /dev/null
+++ b/test/examples/t/filter_discussions/CMakeLists.txt
@@ -0,0 +1,10 @@
+
+add_test(NAME examples_filter_discussions
+         COMMAND osmium_filter_discussions ${CMAKE_CURRENT_SOURCE_DIR}/changesets.osm -)
+
+set_tests_properties(examples_filter_discussions PROPERTIES
+                     FAIL_REGULAR_EXPRESSION "<changeset id=\"402757\"")
+
+set_tests_properties(examples_filter_discussions PROPERTIES
+                     PASS_REGULAR_EXPRESSION "<changeset id=\"402758\"")
+
diff --git a/test/examples/t/filter_discussions/changesets.osm b/test/examples/t/filter_discussions/changesets.osm
new file mode 100644
index 0000000..350a717
--- /dev/null
+++ b/test/examples/t/filter_discussions/changesets.osm
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="Osmosis 0.41">
+  <changeset id="402757" created_at="2008-12-15T12:57:57Z" closed_at="2008-12-15T14:00:54Z" open="false" user="mrettig" uid="38842" min_lat="50.8899870" min_lon="8.0981539" max_lat="50.9508652" max_lon="8.1665031" num_changes="33" comments_count="0"/>
+  <changeset id="402758" created_at="2008-07-05T11:17:12Z" closed_at="2008-07-05T12:17:12Z" open="false" user="Jaycos" uid="45048" min_lat="51.7305590" min_lon="6.5904108" max_lat="51.7305590" max_lon="6.5904108" num_changes="1" comments_count="1">
+    <discussion>
+      <comment uid="123" user="foobar" date="2008-07-05T11:17:13Z">
+        <text>fake comment</text>
+      </comment>
+    </discussion>
+  </changeset>
+</osm>
diff --git a/test/examples/t/index_lookup/CMakeLists.txt b/test/examples/t/index_lookup/CMakeLists.txt
new file mode 100644
index 0000000..ac4c434
--- /dev/null
+++ b/test/examples/t/index_lookup/CMakeLists.txt
@@ -0,0 +1,46 @@
+
+add_test(NAME examples_index_lookup_help
+         COMMAND osmium_index_lookup -h)
+
+set_tests_properties(examples_index_lookup_help PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Usage: osmium_index_lookup")
+
+# Fails with message if index file doesn't exist
+add_test(NAME examples_index_lookup_no_file
+         COMMAND osmium_index_lookup --list=file_does_not_exist --type=location --dump)
+
+set_tests_properties(examples_index_lookup_no_file PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Can not open file")
+
+
+# Fails with message if --type option is not used
+add_test(NAME examples_index_lookup_no_type_option
+         COMMAND osmium_index_lookup --list=file_does_not_exist --dump)
+
+set_tests_properties(examples_index_lookup_no_type_option PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Need --type argument.")
+
+
+# Fails with message if --type option is used with unknown type
+add_test(NAME examples_index_lookup_unknown_type
+         COMMAND osmium_index_lookup --list=file_does_not_exist --type=UNKNOWN --dump)
+
+set_tests_properties(examples_index_lookup_unknown_type PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Unknown type 'UNKNOWN'")
+
+
+# Fails with message when combining options --array and --list
+add_test(NAME examples_index_lookup_array_list
+         COMMAND osmium_index_lookup --list=a --array=b)
+
+set_tests_properties(examples_index_lookup_array_list PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Need option --array or --list, but not both\n$")
+
+
+# Fails with message when combining options --dump and --search
+add_test(NAME examples_index_lookup_dump_search
+         COMMAND osmium_index_lookup --list=x --dump --search=123)
+
+set_tests_properties(examples_index_lookup_dump_search PROPERTIES
+                     PASS_REGULAR_EXPRESSION "Need option --dump or --search, but not both")
+
diff --git a/test/examples/t/location_cache/CMakeLists.txt b/test/examples/t/location_cache/CMakeLists.txt
new file mode 100644
index 0000000..8ca8b79
--- /dev/null
+++ b/test/examples/t/location_cache/CMakeLists.txt
@@ -0,0 +1,36 @@
+
+add_test(NAME examples_location_cache_create
+         COMMAND osmium_location_cache_create ${CMAKE_CURRENT_SOURCE_DIR}/data.osm ${CMAKE_CURRENT_BINARY_DIR}/locations.idx)
+
+
+# Fails with message if index file doesn't exist
+add_test(NAME examples_location_cache_no_file
+         COMMAND osmium_location_cache_use ${CMAKE_CURRENT_SOURCE_DIR}/way.osm ${CMAKE_CURRENT_BINARY_DIR}/file_does_not_exist)
+
+set_tests_properties(examples_location_cache_no_file PROPERTIES
+                     PASS_REGULAR_EXPRESSION "Can not open location cache file")
+
+
+add_test(NAME examples_location_cache_use
+         COMMAND osmium_location_cache_use ${CMAKE_CURRENT_SOURCE_DIR}/way.osm ${CMAKE_CURRENT_BINARY_DIR}/locations.idx)
+
+set_tests_properties(examples_location_cache_use PROPERTIES
+                     DEPENDS examples_location_cache_create
+                     PASS_REGULAR_EXPRESSION "^way 20\n  node 10 \\(7.11,1.01\\)\n  node 11 \\(7.11,1.04\\)\n  node 12 \\(7.14,1.04\\)\n$")
+
+
+add_test(NAME examples_location_cache_dump
+         COMMAND osmium_index_lookup --list=${CMAKE_CURRENT_BINARY_DIR}/locations.idx --type=location --dump)
+
+set_tests_properties(examples_location_cache_dump PROPERTIES
+                     DEPENDS examples_location_cache_create
+                     PASS_REGULAR_EXPRESSION "^10 \\(7.11,1.01\\)\n11 \\(7.11,1.04\\)\n12 \\(7.14,1.04\\)\n13 \\(7.14,1.01\\)\n$")
+
+
+add_test(NAME examples_location_cache_search
+         COMMAND osmium_index_lookup --list=${CMAKE_CURRENT_BINARY_DIR}/locations.idx --type=location --search=12)
+
+set_tests_properties(examples_location_cache_search PROPERTIES
+                     DEPENDS examples_location_cache_create
+                     PASS_REGULAR_EXPRESSION "^12 \\(7.14,1.04\\)\n$")
+
diff --git a/test/examples/t/location_cache/data.osm b/test/examples/t/location_cache/data.osm
new file mode 100644
index 0000000..04cb6fd
--- /dev/null
+++ b/test/examples/t/location_cache/data.osm
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="10" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.01"/>
+    <node id="11" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.04"/>
+    <node id="12" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.04"/>
+    <node id="13" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.01"/>
+</osm>
diff --git a/test/examples/t/location_cache/way.osm b/test/examples/t/location_cache/way.osm
new file mode 100644
index 0000000..f64a170
--- /dev/null
+++ b/test/examples/t/location_cache/way.osm
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <way id="20" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="10"/>
+        <nd ref="11"/>
+        <nd ref="12"/>
+        <tag k="highway" v="primary"/>
+    </way>
+</osm>
diff --git a/test/examples/t/pub_names/CMakeLists.txt b/test/examples/t/pub_names/CMakeLists.txt
index 9a68ae8..3abefa3 100644
--- a/test/examples/t/pub_names/CMakeLists.txt
+++ b/test/examples/t/pub_names/CMakeLists.txt
@@ -1,7 +1,21 @@
 
-add_test(NAME examples_pub_names
-         COMMAND osmium_pub_names ${CMAKE_CURRENT_SOURCE_DIR}/pubs.osm)
-
-set_tests_properties(examples_pub_names PROPERTIES
+add_test(NAME examples_pub_names_node
+         COMMAND osmium_pub_names ${CMAKE_CURRENT_SOURCE_DIR}/pub-node.osm)
+set_tests_properties(examples_pub_names_node PROPERTIES
                      PASS_REGULAR_EXPRESSION "^Im Holze\n$")
 
+add_test(NAME examples_pub_names_way
+         COMMAND osmium_pub_names ${CMAKE_CURRENT_SOURCE_DIR}/pub-way.osm)
+set_tests_properties(examples_pub_names_way PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Vereinsheim\n$")
+
+add_test(NAME examples_pub_names_noname
+         COMMAND osmium_pub_names ${CMAKE_CURRENT_SOURCE_DIR}/pub-noname.osm)
+set_tests_properties(examples_pub_names_noname PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^pub with unknown name\n$")
+
+add_test(NAME examples_pub_names_addr
+         COMMAND osmium_pub_names ${CMAKE_CURRENT_SOURCE_DIR}/pub-addr.osm)
+set_tests_properties(examples_pub_names_addr PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^Im Holze\n  addr:city: Bremen\n")
+
diff --git a/test/examples/t/pub_names/pubs.osm b/test/examples/t/pub_names/pub-addr.osm
similarity index 69%
copy from test/examples/t/pub_names/pubs.osm
copy to test/examples/t/pub_names/pub-addr.osm
index ee54226..6199961 100644
--- a/test/examples/t/pub_names/pubs.osm
+++ b/test/examples/t/pub_names/pub-addr.osm
@@ -1,6 +1,9 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <osm version="0.6">
   <node id="167199652" version="3" timestamp="2010-12-27T13:15:02Z" uid="57645" user="KartoGrapHiti" changeset="6777507" lat="53.0526516" lon="8.8919477">
+    <tag k="addr:city" v="Bremen"/>
+    <tag k="addr:housenumber" v="28"/>
+    <tag k="addr:street" v="Kleine Westerholzstraße"/>
     <tag k="amenity" v="pub"/>
     <tag k="name" v="Im Holze"/>
   </node>
diff --git a/test/examples/t/pub_names/pubs.osm b/test/examples/t/pub_names/pub-node.osm
similarity index 100%
copy from test/examples/t/pub_names/pubs.osm
copy to test/examples/t/pub_names/pub-node.osm
diff --git a/test/examples/t/pub_names/pubs.osm b/test/examples/t/pub_names/pub-noname.osm
similarity index 88%
rename from test/examples/t/pub_names/pubs.osm
rename to test/examples/t/pub_names/pub-noname.osm
index ee54226..3217889 100644
--- a/test/examples/t/pub_names/pubs.osm
+++ b/test/examples/t/pub_names/pub-noname.osm
@@ -2,6 +2,5 @@
 <osm version="0.6">
   <node id="167199652" version="3" timestamp="2010-12-27T13:15:02Z" uid="57645" user="KartoGrapHiti" changeset="6777507" lat="53.0526516" lon="8.8919477">
     <tag k="amenity" v="pub"/>
-    <tag k="name" v="Im Holze"/>
   </node>
 </osm>
diff --git a/test/examples/t/pub_names/pub-way.osm b/test/examples/t/pub_names/pub-way.osm
new file mode 100644
index 0000000..aec7a86
--- /dev/null
+++ b/test/examples/t/pub_names/pub-way.osm
@@ -0,0 +1,21 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6">
+  <node id="498785553" version="4" timestamp="2012-08-05T19:02:18Z" uid="342705" user="KonB" changeset="12624818" lat="53.5657289" lon="8.5727548"/>
+  <node id="498785555" version="4" timestamp="2012-08-05T19:02:18Z" uid="342705" user="KonB" changeset="12624818" lat="53.5657308" lon="8.5730408"/>
+  <node id="498785556" version="4" timestamp="2012-08-05T19:02:18Z" uid="342705" user="KonB" changeset="12624818" lat="53.5656184" lon="8.5730429"/>
+  <node id="498785558" version="4" timestamp="2012-08-05T19:02:18Z" uid="342705" user="KonB" changeset="12624818" lat="53.5656176" lon="8.5729218"/>
+  <node id="498785560" version="4" timestamp="2012-08-05T19:02:18Z" uid="342705" user="KonB" changeset="12624818" lat="53.5654994" lon="8.572924"/>
+  <node id="498785561" version="4" timestamp="2012-08-05T19:02:18Z" uid="342705" user="KonB" changeset="12624818" lat="53.5654983" lon="8.5727591"/>
+  <way id="40948654" version="2" timestamp="2009-09-17T09:27:41Z" uid="42429" user="42429" changeset="2509937">
+    <nd ref="498785553"/>
+    <nd ref="498785555"/>
+    <nd ref="498785556"/>
+    <nd ref="498785558"/>
+    <nd ref="498785560"/>
+    <nd ref="498785561"/>
+    <nd ref="498785553"/>
+    <tag k="name" v="Vereinsheim"/>
+    <tag k="amenity" v="pub"/>
+    <tag k="building" v="yes"/>
+  </way>
+</osm>
diff --git a/test/examples/t/read/CMakeLists.txt b/test/examples/t/read/CMakeLists.txt
new file mode 100644
index 0000000..ff2b7aa
--- /dev/null
+++ b/test/examples/t/read/CMakeLists.txt
@@ -0,0 +1,4 @@
+
+add_test(NAME examples_read
+         COMMAND osmium_read ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
diff --git a/test/examples/t/read/data.osm b/test/examples/t/read/data.osm
new file mode 100644
index 0000000..98462b6
--- /dev/null
+++ b/test/examples/t/read/data.osm
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="701000" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.01"/>
+    <node id="701001" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.04"/>
+    <node id="701002" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.04"/>
+    <node id="701003" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.01"/>
+    <way id="701800" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701000"/>
+        <nd ref="701001"/>
+        <nd ref="701002"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <way id="701801" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701002"/>
+        <nd ref="701003"/>
+        <nd ref="701000"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <relation id="701900" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701800" role="outer"/>
+        <member type="way" ref="701801" role="outer"/>
+        <tag k="type" v="multipolygon"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+</osm>
diff --git a/test/examples/t/read_with_progress/CMakeLists.txt b/test/examples/t/read_with_progress/CMakeLists.txt
new file mode 100644
index 0000000..2bc0881
--- /dev/null
+++ b/test/examples/t/read_with_progress/CMakeLists.txt
@@ -0,0 +1,4 @@
+
+add_test(NAME examples_read_with_progress
+         COMMAND osmium_read_with_progress ${CMAKE_CURRENT_SOURCE_DIR}/data.osm)
+
diff --git a/test/examples/t/read_with_progress/data.osm b/test/examples/t/read_with_progress/data.osm
new file mode 100644
index 0000000..98462b6
--- /dev/null
+++ b/test/examples/t/read_with_progress/data.osm
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="701000" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.01"/>
+    <node id="701001" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.11" lat="1.04"/>
+    <node id="701002" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.04"/>
+    <node id="701003" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lon="7.14" lat="1.01"/>
+    <way id="701800" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701000"/>
+        <nd ref="701001"/>
+        <nd ref="701002"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <way id="701801" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <nd ref="701002"/>
+        <nd ref="701003"/>
+        <nd ref="701000"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+    </way>
+    <relation id="701900" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <member type="way" ref="701800" role="outer"/>
+        <member type="way" ref="701801" role="outer"/>
+        <tag k="type" v="multipolygon"/>
+        <tag k="test:section" v="mp-geom"/>
+        <tag k="test:id" v="701"/>
+        <tag k="landuse" v="forest"/>
+    </relation>
+</osm>
diff --git a/test/examples/t/tiles/CMakeLists.txt b/test/examples/t/tiles/CMakeLists.txt
new file mode 100644
index 0000000..5a4fbaa
--- /dev/null
+++ b/test/examples/t/tiles/CMakeLists.txt
@@ -0,0 +1,18 @@
+
+add_test(NAME examples_tiles_zoom_too_large
+         COMMAND osmium_tiles 50 1 1)
+
+set_tests_properties(examples_tiles_zoom_too_large PROPERTIES WILL_FAIL true)
+
+add_test(NAME examples_tiles_location_invalid
+         COMMAND osmium_tiles 1 200 200)
+
+set_tests_properties(examples_tiles_location_invalid PROPERTIES WILL_FAIL true)
+
+add_test(NAME examples_tiles_okay
+         COMMAND osmium_tiles 8 55.3 11.7)
+
+set_tests_properties(examples_tiles_okay PROPERTIES
+                     PASS_REGULAR_EXPRESSION "^WGS84:    lon=55.3 lat=11.7\nMercator: x=.*\nTile:     zoom=8 x=167 y=119\n$"
+)
+
diff --git a/test/include/catch.hpp b/test/include/catch.hpp
index 6f9334b..7c351e9 100644
--- a/test/include/catch.hpp
+++ b/test/include/catch.hpp
@@ -1,6 +1,6 @@
 /*
- *  Catch v1.8.1
- *  Generated: 2017-03-01 16:04:19.016511
+ *  Catch v1.9.7
+ *  Generated: 2017-08-10 23:49:15.233907
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -40,11 +40,7 @@
 #elif defined __GNUC__
 #    pragma GCC diagnostic ignored "-Wvariadic-macros"
 #    pragma GCC diagnostic ignored "-Wunused-variable"
-
-     // For newer version we can use __Pragma to disable the warnings locally
-#    if __GNUC__ == 4 && __GNUC_MINOR__ >= 4 && __GNUC_MINOR__ <= 7
-#        pragma GCC diagnostic ignored "-Wparentheses"
-#    endif
+#    pragma GCC diagnostic ignored "-Wparentheses"
 
 #    pragma GCC diagnostic push
 #    pragma GCC diagnostic ignored "-Wpadded"
@@ -124,6 +120,12 @@
 #  endif
 
 #   if defined(CATCH_CPP11_OR_GREATER)
+#       define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
 #       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
             _Pragma( "clang diagnostic push" ) \
             _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
@@ -134,13 +136,24 @@
 #endif // __clang__
 
 ////////////////////////////////////////////////////////////////////////////////
-// Cygwin
-#ifdef __CYGWIN__
+// We know some environments not to support full POSIX signals
+#if defined(__CYGWIN__) || defined(__QNX__)
 
 #   if !defined(CATCH_CONFIG_POSIX_SIGNALS)
 #       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
 #   endif
 
+#endif
+
+#ifdef __OS400__
+#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#       define CATCH_CONFIG_COLOUR_NONE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Cygwin
+#ifdef __CYGWIN__
+
 // Required for some versions of Cygwin to declare gettimeofday
 // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
 #   define _BSD_SOURCE
@@ -169,22 +182,10 @@
 // GCC
 #ifdef __GNUC__
 
-#   if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
-#       define CATCH_GCC_HAS_NEW_PRAGMA
-#   endif
-
 #   if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
 #       define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #   endif
 
-#   if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_GCC_HAS_NEW_PRAGMA)
-#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
-            _Pragma( "GCC diagnostic push" ) \
-            _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" )
-#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
-            _Pragma( "GCC diagnostic pop" )
-#   endif
-
 // - otherwise more recent versions define __cplusplus >= 201103L
 // and will get picked up below
 
@@ -224,7 +225,7 @@
 
 // Use __COUNTER__ if the compiler supports it
 #if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \
-    ( defined __GNUC__  && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \
+    ( defined __GNUC__  && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \
     ( defined __clang__ && __clang_major__ >= 3 )
 
 #define CATCH_INTERNAL_CONFIG_COUNTER
@@ -332,6 +333,10 @@
 #   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
 #   define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
 #endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
+#endif
 
 // noexcept support:
 #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT)
@@ -414,14 +419,14 @@ namespace Catch {
     };
 
     template<typename ContainerT>
-    inline void deleteAll( ContainerT& container ) {
+    void deleteAll( ContainerT& container ) {
         typename ContainerT::const_iterator it = container.begin();
         typename ContainerT::const_iterator itEnd = container.end();
         for(; it != itEnd; ++it )
             delete *it;
     }
     template<typename AssociativeContainerT>
-    inline void deleteAllValues( AssociativeContainerT& container ) {
+    void deleteAllValues( AssociativeContainerT& container ) {
         typename AssociativeContainerT::const_iterator it = container.begin();
         typename AssociativeContainerT::const_iterator itEnd = container.end();
         for(; it != itEnd; ++it )
@@ -501,7 +506,6 @@ namespace Catch {
     {
     public:
         NotImplementedException( SourceLineInfo const& lineInfo );
-        NotImplementedException( NotImplementedException const& ) {}
 
         virtual ~NotImplementedException() CATCH_NOEXCEPT {}
 
@@ -770,59 +774,76 @@ void registerTestCaseFunction
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
         static void TestName(); \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         static void TestName()
     #define INTERNAL_CATCH_TESTCASE( ... ) \
         INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); }
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
         namespace{ \
             struct TestName : ClassName{ \
                 void test(); \
             }; \
-            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \
         } \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         void TestName::test()
     #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
         INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
-        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) );
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
 #else
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \
         static void TestName(); \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         static void TestName()
     #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \
         INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \
-        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); }
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
         namespace{ \
             struct TestCaseName : ClassName{ \
                 void test(); \
             }; \
-            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \
         } \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \
         void TestCaseName::test()
     #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\
         INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \
-        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) );
+        CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \
+        Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
+
 #endif
 
 // #included from: internal/catch_capture.hpp
@@ -910,22 +931,24 @@ namespace Catch {
         template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& );
         template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& );
 
-	private:
-		DecomposedExpression& operator = (DecomposedExpression const&);
+    private:
+        DecomposedExpression& operator = (DecomposedExpression const&);
     };
 
     struct AssertionInfo
     {
-        AssertionInfo() {}
-        AssertionInfo(  std::string const& _macroName,
+        AssertionInfo();
+        AssertionInfo(  char const * _macroName,
                         SourceLineInfo const& _lineInfo,
-                        std::string const& _capturedExpression,
-                        ResultDisposition::Flags _resultDisposition );
+                        char const * _capturedExpression,
+                        ResultDisposition::Flags _resultDisposition,
+                        char const * _secondArg = "");
 
-        std::string macroName;
+        char const * macroName;
         SourceLineInfo lineInfo;
-        std::string capturedExpression;
+        char const * capturedExpression;
         ResultDisposition::Flags resultDisposition;
+        char const * secondArg;
     };
 
     struct AssertionResultData
@@ -1021,16 +1044,24 @@ namespace Matchers {
             }
 
         protected:
+            virtual ~MatcherUntypedBase();
             virtual std::string describe() const = 0;
             mutable std::string m_cachedToString;
         private:
             MatcherUntypedBase& operator = ( MatcherUntypedBase const& );
         };
 
-        template<typename ObjectT, typename ComparatorT = ObjectT>
-        struct MatcherBase : MatcherUntypedBase {
-
+        template<typename ObjectT>
+        struct MatcherMethod {
             virtual bool match( ObjectT const& arg ) const = 0;
+        };
+        template<typename PtrT>
+        struct MatcherMethod<PtrT*> {
+            virtual bool match( PtrT* arg ) const = 0;
+        };
+
+        template<typename ObjectT, typename ComparatorT = ObjectT>
+        struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> {
 
             MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const;
             MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const;
@@ -1131,23 +1162,23 @@ namespace Matchers {
     // This allows the types to be inferred
     // - deprecated: prefer ||, && and !
     template<typename T>
-    inline Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) {
+    Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) {
         return Impl::MatchNotOf<T>( underlyingMatcher );
     }
     template<typename T>
-    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
+    Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
         return Impl::MatchAllOf<T>() && m1 && m2;
     }
     template<typename T>
-    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
+    Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
         return Impl::MatchAllOf<T>() && m1 && m2 && m3;
     }
     template<typename T>
-    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
+    Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
         return Impl::MatchAnyOf<T>() || m1 || m2;
     }
     template<typename T>
-    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
+    Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
         return Impl::MatchAnyOf<T>() || m1 || m2 || m3;
     }
 
@@ -1184,6 +1215,7 @@ namespace Catch {
                         char const* capturedExpression,
                         ResultDisposition::Flags resultDisposition,
                         char const* secondArg = "" );
+        ~ResultBuilder();
 
         template<typename T>
         ExpressionLhs<T const&> operator <= ( T const& operand );
@@ -1191,7 +1223,7 @@ namespace Catch {
 
         template<typename T>
         ResultBuilder& operator << ( T const& value ) {
-            m_stream.oss << value;
+            stream().oss << value;
             return *this;
         }
 
@@ -1218,13 +1250,33 @@ namespace Catch {
         template<typename ArgT, typename MatcherT>
         void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString );
 
+        void setExceptionGuard();
+        void unsetExceptionGuard();
+
     private:
         AssertionInfo m_assertionInfo;
         AssertionResultData m_data;
-        CopyableStream m_stream;
+
+        CopyableStream &stream()
+        {
+            if(!m_usedStream)
+            {
+                m_usedStream = true;
+                m_stream().oss.str("");
+            }
+            return m_stream();
+        }
+
+        static CopyableStream &m_stream()
+        {
+            static CopyableStream s;
+            return s;
+        }
 
         bool m_shouldDebugBreak;
         bool m_shouldThrow;
+        bool m_guardException;
+        bool m_usedStream;
     };
 
 } // namespace Catch
@@ -1265,7 +1317,7 @@ namespace Internal {
     template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } };
 
     template<typename T>
-    inline T& opCast(T const& t) { return const_cast<T&>(t); }
+    T& opCast(T const& t) { return const_cast<T&>(t); }
 
 // nullptr_t support based on pull request #154 from Konstantin Baumann
 #ifdef CATCH_CONFIG_CPP11_NULLPTR
@@ -1275,7 +1327,7 @@ namespace Internal {
     // So the compare overloads can be operator agnostic we convey the operator as a template
     // enum, which is used to specialise an Evaluator for doing the comparison.
     template<typename T1, typename T2, Operator Op>
-    class Evaluator{};
+    struct Evaluator{};
 
     template<typename T1, typename T2>
     struct Evaluator<T1, T2, IsEqualTo> {
@@ -1541,7 +1593,7 @@ std::string toString( std::nullptr_t );
 
 #ifdef __OBJC__
     std::string toString( NSString const * const& nsstring );
-    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+    std::string toString( NSString * CATCH_ARC_STRONG & nsstring );
     std::string toString( NSObject* const& nsObject );
 #endif
 
@@ -1549,6 +1601,7 @@ namespace Detail {
 
     extern const std::string unprintableString;
 
+ #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK)
     struct BorgType {
         template<typename T> BorgType( T const& );
     };
@@ -1567,6 +1620,20 @@ namespace Detail {
         static T  const&t;
         enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
     };
+#else
+    template<typename T>
+    class IsStreamInsertable {
+        template<typename SS, typename TT>
+        static auto test(int)
+        -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() );
+
+        template<typename, typename>
+        static auto test(...) -> std::false_type;
+
+    public:
+        static const bool value = decltype(test<std::ostream,const T&>(0))::value;
+    };
+#endif
 
 #if defined(CATCH_CONFIG_CPP11_IS_ENUM)
     template<typename T,
@@ -1615,7 +1682,7 @@ namespace Detail {
     std::string rawMemoryToString( const void *object, std::size_t size );
 
     template<typename T>
-    inline std::string rawMemoryToString( const T& object ) {
+    std::string rawMemoryToString( const T& object ) {
       return rawMemoryToString( &object, sizeof(object) );
     }
 
@@ -1810,7 +1877,7 @@ public:
     }
 
     virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
-        dest = Catch::toString( m_truthy );
+        dest = Catch::toString( m_lhs );
     }
 
 private:
@@ -1904,7 +1971,7 @@ private:
 namespace Catch {
 
     template<typename T>
-    inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
+    ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
         return ExpressionLhs<T const&>( *this, operand );
     }
 
@@ -1913,7 +1980,7 @@ namespace Catch {
     }
 
     template<typename ArgT, typename MatcherT>
-    inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher,
+    void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher,
                                              char const* matcherString ) {
         MatchExpression<ArgT const&, MatcherT const&> expr( arg, matcher, matcherString );
         setResultType( matcher.match( arg ) );
@@ -2009,7 +2076,13 @@ namespace Catch {
         virtual std::string getCurrentTestName() const = 0;
         virtual const AssertionResult* getLastResult() const = 0;
 
+        virtual void exceptionEarlyReported() = 0;
+
         virtual void handleFatalErrorCondition( std::string const& message ) = 0;
+
+        virtual bool lastAssertionPassed() = 0;
+        virtual void assertionPassed() = 0;
+        virtual void assertionRun() = 0;
     };
 
     IResultCapture& getResultCapture();
@@ -2052,9 +2125,9 @@ namespace Catch{
     #if defined(__ppc64__) || defined(__ppc__)
         #define CATCH_TRAP() \
                 __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
-                : : : "memory","r0","r3","r4" )
+                : : : "memory","r0","r3","r4" ) /* NOLINT */
     #else
-        #define CATCH_TRAP() __asm__("int $3\n" : : )
+        #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ )
     #endif
 
 #elif defined(CATCH_PLATFORM_LINUX)
@@ -2062,7 +2135,7 @@ namespace Catch{
     // directly at the location of the failing check instead of breaking inside
     // raise() called from it, i.e. one stack frame below.
     #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
-        #define CATCH_TRAP() asm volatile ("int $3")
+        #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */
     #else // Fall back to the generic way.
         #include <signal.h>
 
@@ -2093,45 +2166,6 @@ namespace Catch {
     };
 }
 
-// #included from: catch_type_traits.hpp
-#define TWOBLUECUBES_CATCH_TYPE_TRAITS_HPP_INCLUDED
-
-#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
-#include <type_traits>
-#endif
-
-namespace Catch {
-
-#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
-
-     template <typename T>
-     using add_lvalue_reference = std::add_lvalue_reference<T>;
-
-     template <typename T>
-     using add_const = std::add_const<T>;
-
-#else
-
-    template <typename T>
-    struct add_const {
-        typedef const T type;
-    };
-
-    template <typename T>
-    struct add_lvalue_reference {
-        typedef T& type;
-    };
-    template <typename T>
-    struct add_lvalue_reference<T&> {
-        typedef T& type;
-    };
-    // No && overload, because that is C++11, in which case we have
-    // proper type_traits implementation from the standard library
-
-#endif
-
-}
-
 #if defined(CATCH_CONFIG_FAST_COMPILE)
 ///////////////////////////////////////////////////////////////////////////////
 // We can speedup compilation significantly by breaking into debugger lower in
@@ -2139,6 +2173,33 @@ namespace Catch {
 // macro in each assertion
 #define INTERNAL_CATCH_REACT( resultBuilder ) \
     resultBuilder.react();
+
+///////////////////////////////////////////////////////////////////////////////
+// Another way to speed-up compilation is to omit local try-catch for REQUIRE*
+// macros.
+// This can potentially cause false negative, if the test code catches
+// the exception before it propagates back up to the runner.
+#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+        __catchResult.setExceptionGuard(); \
+        CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+        ( __catchResult <= expr ).endExpression(); \
+        CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+        __catchResult.unsetExceptionGuard(); \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
+
+#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \
+    do { \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
+        __catchResult.setExceptionGuard(); \
+        __catchResult.captureMatch( arg, matcher, #matcher ); \
+        __catchResult.unsetExceptionGuard(); \
+        INTERNAL_CATCH_REACT( __catchResult ) \
+    } while( Catch::alwaysFalse() )
+
 #else
 ///////////////////////////////////////////////////////////////////////////////
 // In the event of a failure works out if the debugger needs to be invoked
@@ -2151,7 +2212,7 @@ namespace Catch {
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \
+#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
@@ -2167,17 +2228,17 @@ namespace Catch {
     // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
-    INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
-    if( Catch::getResultCapture().getLastResult()->succeeded() )
+#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \
+    if( Catch::getResultCapture().lastAssertionPassed() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \
-    INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
-    if( !Catch::getResultCapture().getLastResult()->succeeded() )
+#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \
+    if( !Catch::getResultCapture().lastAssertionPassed() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \
+#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
@@ -2191,7 +2252,7 @@ namespace Catch {
     } while( Catch::alwaysFalse() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \
+#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \
         if( __catchResult.allowThrows() ) \
@@ -2208,7 +2269,7 @@ namespace Catch {
     } while( Catch::alwaysFalse() )
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \
+#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \
         if( __catchResult.allowThrows() ) \
@@ -2216,7 +2277,7 @@ namespace Catch {
                 static_cast<void>(expr); \
                 __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
             } \
-            catch( Catch::add_const<Catch::add_lvalue_reference<exceptionType>::type>::type ) { \
+            catch( exceptionType ) { \
                 __catchResult.captureResult( Catch::ResultWas::Ok ); \
             } \
             catch( ... ) { \
@@ -2229,7 +2290,7 @@ namespace Catch {
 
 ///////////////////////////////////////////////////////////////////////////////
 #ifdef CATCH_CONFIG_VARIADIC_MACROS
-    #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \
+    #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
         do { \
             Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
             __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \
@@ -2237,7 +2298,7 @@ namespace Catch {
             INTERNAL_CATCH_REACT( __catchResult ) \
         } while( Catch::alwaysFalse() )
 #else
-    #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \
+    #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \
         do { \
             Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
             __catchResult << log + ::Catch::StreamEndStop(); \
@@ -2247,11 +2308,11 @@ namespace Catch {
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_INFO( log, macroName ) \
+#define INTERNAL_CATCH_INFO( macroName, log ) \
     Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log;
 
 ///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
+#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
         try { \
@@ -2368,14 +2429,19 @@ namespace Catch {
 // #included from: catch_timer.h
 #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED
 
-#ifdef CATCH_PLATFORM_WINDOWS
-typedef unsigned long long uint64_t;
+#ifdef _MSC_VER
+
+namespace Catch {
+    typedef unsigned long long UInt64;
+}
 #else
 #include <stdint.h>
+namespace Catch {
+    typedef uint64_t UInt64;
+}
 #endif
 
 namespace Catch {
-
     class Timer {
     public:
         Timer() : m_ticks( 0 ) {}
@@ -2385,7 +2451,7 @@ namespace Catch {
         double getElapsedSeconds() const;
 
     private:
-        uint64_t m_ticks;
+        UInt64 m_ticks;
     };
 
 } // namespace Catch
@@ -2424,7 +2490,6 @@ namespace Catch {
 // #included from: internal/catch_generators.hpp
 #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
 
-#include <iterator>
 #include <vector>
 #include <string>
 #include <stdlib.h>
@@ -2538,7 +2603,7 @@ public:
 private:
 
     void move( CompositeGenerator& other ) {
-        std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) );
+        m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() );
         m_totalSize += other.m_totalSize;
         other.m_composed.clear();
     }
@@ -2620,12 +2685,15 @@ namespace Catch {
     struct IExceptionTranslator;
     struct IReporterRegistry;
     struct IReporterFactory;
+    struct ITagAliasRegistry;
 
     struct IRegistryHub {
         virtual ~IRegistryHub();
 
         virtual IReporterRegistry const& getReporterRegistry() const = 0;
         virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
+
         virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
     };
 
@@ -2635,6 +2703,7 @@ namespace Catch {
         virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0;
         virtual void registerTest( TestCase const& testInfo ) = 0;
         virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
     };
 
     IRegistryHub& getRegistryHub();
@@ -2726,26 +2795,25 @@ namespace Detail {
             m_value( value )
         {}
 
-        Approx( Approx const& other )
-        :   m_epsilon( other.m_epsilon ),
-            m_margin( other.m_margin ),
-            m_scale( other.m_scale ),
-            m_value( other.m_value )
-        {}
-
         static Approx custom() {
             return Approx( 0 );
         }
 
-        Approx operator()( double value ) {
-            Approx approx( value );
+#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx operator()( T value ) {
+            Approx approx( static_cast<double>(value) );
             approx.epsilon( m_epsilon );
             approx.margin( m_margin );
             approx.scale( m_scale );
             return approx;
         }
 
-#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        explicit Approx( T value ): Approx(static_cast<double>(value))
+        {}
+
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
         friend bool operator == ( const T& lhs, Approx const& rhs ) {
             // Thanks to Richard Harris for his help refining this formula
@@ -2773,29 +2841,53 @@ namespace Detail {
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator <= ( T lhs, Approx const& rhs )
-        {
-          return double(lhs) < rhs.m_value || lhs == rhs;
+        friend bool operator <= ( T lhs, Approx const& rhs ) {
+            return double(lhs) < rhs.m_value || lhs == rhs;
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator <= ( Approx const& lhs, T rhs )
-        {
-          return lhs.m_value < double(rhs) || lhs == rhs;
+        friend bool operator <= ( Approx const& lhs, T rhs ) {
+            return lhs.m_value < double(rhs) || lhs == rhs;
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator >= ( T lhs, Approx const& rhs )
-        {
-          return double(lhs) > rhs.m_value || lhs == rhs;
+        friend bool operator >= ( T lhs, Approx const& rhs ) {
+            return double(lhs) > rhs.m_value || lhs == rhs;
         }
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        friend bool operator >= ( Approx const& lhs, T rhs )
-        {
-          return lhs.m_value > double(rhs) || lhs == rhs;
+        friend bool operator >= ( Approx const& lhs, T rhs ) {
+            return lhs.m_value > double(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& epsilon( T newEpsilon ) {
+            m_epsilon = double(newEpsilon);
+            return *this;
         }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& margin( T newMargin ) {
+            m_margin = double(newMargin);
+            return *this;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& scale( T newScale ) {
+            m_scale = double(newScale);
+            return *this;
+        }
+
 #else
+
+        Approx operator()( double value ) {
+            Approx approx( value );
+            approx.epsilon( m_epsilon );
+            approx.margin( m_margin );
+            approx.scale( m_scale );
+            return approx;
+        }
+
         friend bool operator == ( double lhs, Approx const& rhs ) {
             // Thanks to Richard Harris for his help refining this formula
             bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) );
@@ -2817,26 +2909,21 @@ namespace Detail {
             return !operator==( rhs, lhs );
         }
 
-        friend bool operator <= ( double lhs, Approx const& rhs )
-        {
-          return lhs < rhs.m_value || lhs == rhs;
+        friend bool operator <= ( double lhs, Approx const& rhs ) {
+            return lhs < rhs.m_value || lhs == rhs;
         }
 
-        friend bool operator <= ( Approx const& lhs, double rhs )
-        {
-          return lhs.m_value < rhs || lhs == rhs;
+        friend bool operator <= ( Approx const& lhs, double rhs ) {
+            return lhs.m_value < rhs || lhs == rhs;
         }
 
-        friend bool operator >= ( double lhs, Approx const& rhs )
-        {
-          return lhs > rhs.m_value || lhs == rhs;
+        friend bool operator >= ( double lhs, Approx const& rhs ) {
+            return lhs > rhs.m_value || lhs == rhs;
         }
 
-        friend bool operator >= ( Approx const& lhs, double rhs )
-        {
-          return lhs.m_value > rhs || lhs == rhs;
+        friend bool operator >= ( Approx const& lhs, double rhs ) {
+            return lhs.m_value > rhs || lhs == rhs;
         }
-#endif
 
         Approx& epsilon( double newEpsilon ) {
             m_epsilon = newEpsilon;
@@ -2852,6 +2939,7 @@ namespace Detail {
             m_scale = newScale;
             return *this;
         }
+#endif
 
         std::string toString() const {
             std::ostringstream oss;
@@ -2893,7 +2981,7 @@ namespace Matchers {
         };
 
         struct StringMatcherBase : MatcherBase<std::string> {
-            StringMatcherBase( std::string operation, CasedString const& comparator );
+            StringMatcherBase( std::string const& operation, CasedString const& comparator );
             virtual std::string describe() const CATCH_OVERRIDE;
 
             CasedString m_comparator;
@@ -3032,7 +3120,7 @@ namespace Matchers {
 namespace Catch {
 
     struct TagAlias {
-        TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
+        TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
 
         std::string tag;
         SourceLineInfo lineInfo;
@@ -3104,8 +3192,18 @@ namespace Catch {
         }
 
     private:
-        T* nullableValue;
-        char storage[sizeof(T)];
+        T *nullableValue;
+        union {
+            char storage[sizeof(T)];
+
+            // These are here to force alignment for the storage
+            long double dummy1;
+            void (*dummy2)();
+            long double dummy3;
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+            long long dummy4;
+#endif
+        };
     };
 
 } // end namespace Catch
@@ -3304,64 +3402,67 @@ namespace Catch {
         namespace Impl {
         namespace NSStringMatchers {
 
-            template<typename MatcherT>
-            struct StringHolder : MatcherImpl<MatcherT, NSString*>{
+            struct StringHolder : MatcherBase<NSString*>{
                 StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
                 StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
                 StringHolder() {
                     arcSafeRelease( m_substr );
                 }
 
+                virtual bool match( NSString* arg ) const CATCH_OVERRIDE {
+                    return false;
+                }
+
                 NSString* m_substr;
             };
 
-            struct Equals : StringHolder<Equals> {
+            struct Equals : StringHolder {
                 Equals( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const CATCH_OVERRIDE {
                     return  (str != nil || m_substr == nil ) &&
                             [str isEqualToString:m_substr];
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "equals string: " + Catch::toString( m_substr );
                 }
             };
 
-            struct Contains : StringHolder<Contains> {
+            struct Contains : StringHolder {
                 Contains( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const {
                     return  (str != nil || m_substr == nil ) &&
                             [str rangeOfString:m_substr].location != NSNotFound;
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "contains string: " + Catch::toString( m_substr );
                 }
             };
 
-            struct StartsWith : StringHolder<StartsWith> {
+            struct StartsWith : StringHolder {
                 StartsWith( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const {
                     return  (str != nil || m_substr == nil ) &&
                             [str rangeOfString:m_substr].location == 0;
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "starts with: " + Catch::toString( m_substr );
                 }
             };
-            struct EndsWith : StringHolder<EndsWith> {
+            struct EndsWith : StringHolder {
                 EndsWith( NSString* substr ) : StringHolder( substr ){}
 
-                virtual bool match( ExpressionType const& str ) const {
+                virtual bool match( NSString* str ) const {
                     return  (str != nil || m_substr == nil ) &&
                             [str rangeOfString:m_substr].location == [str length] - [m_substr length];
                 }
 
-                virtual std::string toString() const {
+                virtual std::string describe() const CATCH_OVERRIDE {
                     return "ends with: " + Catch::toString( m_substr );
                 }
             };
@@ -3408,16 +3509,16 @@ return @ desc; \
 #include <crtdbg.h>
 class LeakDetector {
 public:
-	LeakDetector() {
-		int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
-		flag |= _CRTDBG_LEAK_CHECK_DF;
-		flag |= _CRTDBG_ALLOC_MEM_DF;
-		_CrtSetDbgFlag(flag);
-		_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
-		_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
-		// Change this to leaking allocation's number to break there
-		_CrtSetBreakAlloc(-1);
-	}
+    LeakDetector() {
+        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+        flag |= _CRTDBG_LEAK_CHECK_DF;
+        flag |= _CRTDBG_ALLOC_MEM_DF;
+        _CrtSetDbgFlag(flag);
+        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+        // Change this to leaking allocation's number to break there
+        _CrtSetBreakAlloc(-1);
+    }
 };
 #else
 class LeakDetector {};
@@ -3617,7 +3718,7 @@ namespace Catch {
         ITagAliasRegistry const* m_tagAliases;
 
     public:
-        TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+        TestSpecParser( ITagAliasRegistry const& tagAliases ) :m_mode(None), m_exclusion(false), m_start(0), m_pos(0), m_tagAliases( &tagAliases ) {}
 
         TestSpecParser& parse( std::string const& arg ) {
             m_mode = None;
@@ -3801,6 +3902,7 @@ namespace Catch {
 
     std::ostream& cout();
     std::ostream& cerr();
+    std::ostream& clog();
 
     struct IStream {
         virtual ~IStream() CATCH_NOEXCEPT;
@@ -3856,6 +3958,7 @@ namespace Catch {
             listTags( false ),
             listReporters( false ),
             listTestNamesOnly( false ),
+            listExtraInfo( false ),
             showSuccessfulTests( false ),
             shouldDebugBreak( false ),
             noThrow( false ),
@@ -3875,6 +3978,7 @@ namespace Catch {
         bool listTags;
         bool listReporters;
         bool listTestNamesOnly;
+        bool listExtraInfo;
 
         bool showSuccessfulTests;
         bool shouldDebugBreak;
@@ -3933,6 +4037,7 @@ namespace Catch {
         bool listTestNamesOnly() const { return m_data.listTestNamesOnly; }
         bool listTags() const { return m_data.listTags; }
         bool listReporters() const { return m_data.listReporters; }
+        bool listExtraInfo() const { return m_data.listExtraInfo; }
 
         std::string getProcessName() const { return m_data.processName; }
 
@@ -4020,6 +4125,7 @@ namespace Catch {
 #include <vector>
 #include <sstream>
 #include <algorithm>
+#include <cctype>
 
 // Use optional outer namespace
 #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
@@ -4129,7 +4235,7 @@ namespace Tbc {
             return oss.str();
         }
 
-        inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+        friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
             for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
                 it != itEnd; ++it ) {
                 if( it != _text.begin() )
@@ -4362,7 +4468,7 @@ namespace Clara {
             _dest = _source;
         }
         char toLowerCh(char c) {
-            return static_cast<char>( ::tolower( c ) );
+            return static_cast<char>( std::tolower( c ) );
         }
         inline void convertInto( std::string const& _source, bool& _dest ) {
             std::string sourceLC = _source;
@@ -4516,12 +4622,13 @@ namespace Clara {
         }
 
         void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) {
-            for( std::size_t i = 0; i <= arg.size(); ++i ) {
+            for( std::size_t i = 0; i < arg.size(); ++i ) {
                 char c = arg[i];
                 if( c == '"' )
                     inQuotes = !inQuotes;
                 mode = handleMode( i, c, arg, tokens );
             }
+            mode = handleMode( arg.size(), '\0', arg, tokens );
         }
         Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
             switch( mode ) {
@@ -4554,6 +4661,7 @@ namespace Clara {
                 default: from = i; return ShortOpt;
             }
         }
+
         Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) {
             if( std::string( ":=\0", 3 ).find( c ) == std::string::npos )
                 return mode;
@@ -4885,7 +4993,7 @@ namespace Clara {
         }
 
         std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const {
-            std::string processName = args[0];
+            std::string processName = args.empty() ? std::string() : args[0];
             std::size_t lastSlash = processName.find_last_of( "/\\" );
             if( lastSlash != std::string::npos )
                 processName = processName.substr( lastSlash+1 );
@@ -5191,6 +5299,10 @@ namespace Catch {
             .describe( "list all/matching test cases names only" )
             .bind( &ConfigData::listTestNamesOnly );
 
+        cli["--list-extra-info"]
+            .describe( "list all/matching test cases with more info" )
+            .bind( &ConfigData::listExtraInfo );
+
         cli["--list-reporters"]
             .describe( "list all reporters" )
             .bind( &ConfigData::listReporters );
@@ -5719,8 +5831,9 @@ namespace Catch {
         }
 
         std::size_t matchedTests = 0;
-        TextAttributes nameAttr, tagsAttr;
+        TextAttributes nameAttr, descAttr, tagsAttr;
         nameAttr.setInitialIndent( 2 ).setIndent( 4 );
+        descAttr.setIndent( 4 );
         tagsAttr.setIndent( 6 );
 
         std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
@@ -5735,6 +5848,13 @@ namespace Catch {
             Colour colourGuard( colour );
 
             Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
+            if( config.listExtraInfo() ) {
+                Catch::cout() << "    " << testCaseInfo.lineInfo << std::endl;
+                std::string description = testCaseInfo.description;
+                if( description.empty() )
+                    description = "(NO DESCRIPTION)";
+                Catch::cout() << Text( description, descAttr ) << std::endl;
+            }
             if( !testCaseInfo.tags.empty() )
                 Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
         }
@@ -5758,9 +5878,12 @@ namespace Catch {
             matchedTests++;
             TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
             if( startsWith( testCaseInfo.name, '#' ) )
-               Catch::cout() << '"' << testCaseInfo.name << '"' << std::endl;
+               Catch::cout() << '"' << testCaseInfo.name << '"';
             else
-               Catch::cout() << testCaseInfo.name << std::endl;
+               Catch::cout() << testCaseInfo.name;
+            if ( config.listExtraInfo() )
+                Catch::cout() << "\t@" << testCaseInfo.lineInfo;
+            Catch::cout() << std::endl;
         }
         return matchedTests;
     }
@@ -5852,7 +5975,7 @@ namespace Catch {
 
     inline Option<std::size_t> list( Config const& config ) {
         Option<std::size_t> listedCount;
-        if( config.listTests() )
+        if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) )
             listedCount = listedCount.valueOr(0) + listTests( config );
         if( config.listTestNamesOnly() )
             listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
@@ -5871,13 +5994,14 @@ namespace Catch {
 // #included from: catch_test_case_tracker.hpp
 #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
 
-#include <map>
+#include <algorithm>
 #include <string>
 #include <assert.h>
 #include <vector>
-#include <iterator>
 #include <stdexcept>
 
+CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
+
 namespace Catch {
 namespace TestCaseTracking {
 
@@ -6148,12 +6272,12 @@ namespace TestCaseTracking {
             if( !filters.empty() ) {
                 m_filters.push_back(""); // Root - should never be consulted
                 m_filters.push_back(""); // Test Case - not a section filter
-                std::copy( filters.begin(), filters.end(), std::back_inserter( m_filters ) );
+                m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
             }
         }
         void addNextFilters( std::vector<std::string> const& filters ) {
             if( filters.size() > 1 )
-                std::copy( filters.begin()+1, filters.end(), std::back_inserter( m_filters ) );
+                m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
         }
     };
 
@@ -6223,6 +6347,8 @@ using TestCaseTracking::IndexTracker;
 
 } // namespace Catch
 
+CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
+
 // #included from: catch_fatal_condition.hpp
 #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
 
@@ -6292,7 +6418,6 @@ namespace Catch {
         static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
             for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
                 if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
-                    reset();
                     reportFatal(signalDefs[i].name);
                 }
             }
@@ -6462,6 +6587,29 @@ namespace Catch {
         std::string& m_targetString;
     };
 
+    // StdErr has two constituent streams in C++, std::cerr and std::clog
+    // This means that we need to redirect 2 streams into 1 to keep proper
+    // order of writes and cannot use StreamRedirect on its own
+    class StdErrRedirect {
+    public:
+        StdErrRedirect(std::string& targetString)
+        :m_cerrBuf( cerr().rdbuf() ), m_clogBuf(clog().rdbuf()),
+        m_targetString(targetString){
+            cerr().rdbuf(m_oss.rdbuf());
+            clog().rdbuf(m_oss.rdbuf());
+        }
+        ~StdErrRedirect() {
+            m_targetString += m_oss.str();
+            cerr().rdbuf(m_cerrBuf);
+            clog().rdbuf(m_clogBuf);
+        }
+    private:
+        std::streambuf* m_cerrBuf;
+        std::streambuf* m_clogBuf;
+        std::ostringstream m_oss;
+        std::string& m_targetString;
+    };
+
     ///////////////////////////////////////////////////////////////////////////
 
     class RunContext : public IResultCapture, public IRunner {
@@ -6476,7 +6624,8 @@ namespace Catch {
             m_context( getCurrentMutableContext() ),
             m_activeTestCase( CATCH_NULL ),
             m_config( _config ),
-            m_reporter( reporter )
+            m_reporter( reporter ),
+            m_shouldReportUnexpected ( true )
         {
             m_context.setRunner( this );
             m_context.setConfig( m_config );
@@ -6554,14 +6703,32 @@ namespace Catch {
                 m_totals.assertions.failed++;
             }
 
-            if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) )
-                m_messages.clear();
+            // We have no use for the return value (whether messages should be cleared), because messages were made scoped
+            // and should be let to clear themselves out.
+            static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));
 
             // Reset working state
-            m_lastAssertionInfo = AssertionInfo( std::string(), m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
+            m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
             m_lastResult = result;
         }
 
+        virtual bool lastAssertionPassed()
+        {
+            return m_totals.assertions.passed == (m_prevPassed + 1);
+        }
+
+        virtual void assertionPassed()
+        {
+            m_totals.assertions.passed++;
+            m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}";
+            m_lastAssertionInfo.macroName = "";
+        }
+
+        virtual void assertionRun()
+        {
+            m_prevPassed = m_totals.assertions.passed;
+        }
+
         virtual bool sectionStarted (
             SectionInfo const& sectionInfo,
             Counts& assertions
@@ -6633,11 +6800,19 @@ namespace Catch {
             return &m_lastResult;
         }
 
+        virtual void exceptionEarlyReported() {
+            m_shouldReportUnexpected = false;
+        }
+
         virtual void handleFatalErrorCondition( std::string const& message ) {
-            ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
-            resultBuilder.setResultType( ResultWas::FatalErrorCondition );
-            resultBuilder << message;
-            resultBuilder.captureExpression();
+            // Don't rebuild the result -- the stringification itself can cause more fatal errors
+            // Instead, fake a result data.
+            AssertionResultData tempResult;
+            tempResult.resultType = ResultWas::FatalErrorCondition;
+            tempResult.message = message;
+            AssertionResult result(m_lastAssertionInfo, tempResult);
+
+            getResultCapture().assertionEnded(result);
 
             handleUnfinishedSections();
 
@@ -6654,6 +6829,7 @@ namespace Catch {
 
             Totals deltaTotals;
             deltaTotals.testCases.failed = 1;
+            deltaTotals.assertions.failed = 1;
             m_reporter->testCaseEnded( TestCaseStats(   testInfo,
                                                         deltaTotals,
                                                         std::string(),
@@ -6678,8 +6854,9 @@ namespace Catch {
             m_reporter->sectionStarting( testCaseSection );
             Counts prevAssertions = m_totals.assertions;
             double duration = 0;
+            m_shouldReportUnexpected = true;
             try {
-                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, std::string(), ResultDisposition::Normal );
+                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
 
                 seedRng( *m_config );
 
@@ -6687,7 +6864,7 @@ namespace Catch {
                 timer.start();
                 if( m_reporter->getPreferences().shouldRedirectStdOut ) {
                     StreamRedirect coutRedir( Catch::cout(), redirectedCout );
-                    StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+                    StdErrRedirect errRedir( redirectedCerr );
                     invokeActiveTestCase();
                 }
                 else {
@@ -6699,7 +6876,11 @@ namespace Catch {
                 // This just means the test was aborted due to failure
             }
             catch(...) {
-                makeUnexpectedResultBuilder().useActiveException();
+                // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
+                // are reported without translation at the point of origin.
+                if (m_shouldReportUnexpected) {
+                    makeUnexpectedResultBuilder().useActiveException();
+                }
             }
             m_testCaseTracker->close();
             handleUnfinishedSections();
@@ -6727,9 +6908,9 @@ namespace Catch {
     private:
 
         ResultBuilder makeUnexpectedResultBuilder() const {
-            return ResultBuilder(   m_lastAssertionInfo.macroName.c_str(),
+            return ResultBuilder(   m_lastAssertionInfo.macroName,
                                     m_lastAssertionInfo.lineInfo,
-                                    m_lastAssertionInfo.capturedExpression.c_str(),
+                                    m_lastAssertionInfo.capturedExpression,
                                     m_lastAssertionInfo.resultDisposition );
         }
 
@@ -6759,6 +6940,8 @@ namespace Catch {
         std::vector<SectionEndInfo> m_unfinishedSections;
         std::vector<ITracker*> m_activeSections;
         TrackerContext m_trackerContext;
+        size_t m_prevPassed;
+        bool m_shouldReportUnexpected;
     };
 
     IResultCapture& getResultCapture() {
@@ -6780,7 +6963,7 @@ namespace Catch {
         Version(    unsigned int _majorVersion,
                     unsigned int _minorVersion,
                     unsigned int _patchNumber,
-                    std::string const& _branchName,
+                    char const * const _branchName,
                     unsigned int _buildNumber );
 
         unsigned int const majorVersion;
@@ -6788,7 +6971,7 @@ namespace Catch {
         unsigned int const patchNumber;
 
         // buildNumber is only used if branchName is not null
-        std::string const branchName;
+        char const * const branchName;
         unsigned int const buildNumber;
 
         friend std::ostream& operator << ( std::ostream& os, Version const& version );
@@ -6797,7 +6980,7 @@ namespace Catch {
         void operator=( Version const& );
     };
 
-    extern Version libraryVersion;
+    inline Version libraryVersion();
 }
 
 #include <fstream>
@@ -6816,10 +6999,14 @@ namespace Catch {
         return reporter;
     }
 
+#if !defined(CATCH_CONFIG_DEFAULT_REPORTER)
+#define CATCH_CONFIG_DEFAULT_REPORTER "console"
+#endif
+
     Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) {
         std::vector<std::string> reporters = config->getReporterNames();
         if( reporters.empty() )
-            reporters.push_back( "console" );
+            reporters.push_back( CATCH_CONFIG_DEFAULT_REPORTER );
 
         Ptr<IStreamingReporter> reporter;
         for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end();
@@ -6879,11 +7066,11 @@ namespace Catch {
             if( lastSlash != std::string::npos )
                 filename = filename.substr( lastSlash+1 );
 
-            std::string::size_type lastDot = filename.find_last_of( "." );
+            std::string::size_type lastDot = filename.find_last_of( '.' );
             if( lastDot != std::string::npos )
                 filename = filename.substr( 0, lastDot );
 
-            tags.insert( "#" + filename );
+            tags.insert( '#' + filename );
             setTags( test, tags );
         }
     }
@@ -6909,7 +7096,7 @@ namespace Catch {
         }
 
         void showHelp( std::string const& processName ) {
-            Catch::cout() << "\nCatch v" << libraryVersion << "\n";
+            Catch::cout() << "\nCatch v" << libraryVersion() << "\n";
 
             m_cli.usage( Catch::cout(), processName );
             Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
@@ -6950,6 +7137,32 @@ namespace Catch {
             return returnCode;
         }
 
+    #if defined(WIN32) && defined(UNICODE)
+        int run( int argc, wchar_t const* const* const argv ) {
+
+            char **utf8Argv = new char *[ argc ];
+
+            for ( int i = 0; i < argc; ++i ) {
+                int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL );
+
+                utf8Argv[ i ] = new char[ bufSize ];
+
+                WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL );
+            }
+
+            int returnCode = applyCommandLine( argc, utf8Argv );
+            if( returnCode == 0 )
+                returnCode = run();
+
+            for ( int i = 0; i < argc; ++i )
+                delete [] utf8Argv[ i ];
+
+            delete [] utf8Argv;
+
+            return returnCode;
+        }
+    #endif
+
         int run() {
             if( m_configData.showHelp )
                 return 0;
@@ -7297,6 +7510,26 @@ namespace Catch {
     };
 }
 
+// #included from: catch_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+    class TagAliasRegistry : public ITagAliasRegistry {
+    public:
+        virtual ~TagAliasRegistry();
+        virtual Option<TagAlias> find( std::string const& alias ) const;
+        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
+        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );
+
+    private:
+        std::map<std::string, TagAlias> m_registry;
+    };
+
+} // end namespace Catch
+
 namespace Catch {
 
     namespace {
@@ -7318,6 +7551,9 @@ namespace Catch {
             virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE {
                 return m_exceptionTranslatorRegistry;
             }
+            virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE {
+                return m_tagAliasRegistry;
+            }
 
         public: // IMutableRegistryHub
             virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
@@ -7332,11 +7568,15 @@ namespace Catch {
             virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE {
                 m_exceptionTranslatorRegistry.registerTranslator( translator );
             }
+            virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE {
+                m_tagAliasRegistry.add( alias, tag, lineInfo );
+            }
 
         private:
             TestRegistry m_testCaseRegistry;
             ReporterRegistry m_reporterRegistry;
             ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+            TagAliasRegistry m_tagAliasRegistry;
         };
 
         // Single, global, instance
@@ -7482,6 +7722,9 @@ namespace Catch {
     std::ostream& cerr() {
         return std::cerr;
     }
+    std::ostream& clog() {
+        return std::clog;
+    }
 #endif
 }
 
@@ -7581,6 +7824,23 @@ namespace Catch {
 // #included from: catch_console_colour_impl.hpp
 #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
 
+// #included from: catch_errno_guard.hpp
+#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED
+
+#include <cerrno>
+
+namespace Catch {
+
+    class ErrnoGuard {
+    public:
+        ErrnoGuard():m_oldErrno(errno){}
+        ~ErrnoGuard() { errno = m_oldErrno; }
+    private:
+        int m_oldErrno;
+    };
+
+}
+
 namespace Catch {
     namespace {
 
@@ -7716,6 +7976,7 @@ namespace {
     };
 
     IColourImpl* platformColourInstance() {
+        ErrnoGuard guard;
         Ptr<IConfig const> config = getCurrentContext().getConfig();
         UseColour::YesOrNo colourMode = config
             ? config->useColour()
@@ -7834,14 +8095,18 @@ namespace Catch {
 
 namespace Catch {
 
-    AssertionInfo::AssertionInfo(   std::string const& _macroName,
+    AssertionInfo::AssertionInfo():macroName(""), capturedExpression(""), resultDisposition(ResultDisposition::Normal), secondArg(""){}
+
+    AssertionInfo::AssertionInfo(   char const * _macroName,
                                     SourceLineInfo const& _lineInfo,
-                                    std::string const& _capturedExpression,
-                                    ResultDisposition::Flags _resultDisposition )
+                                    char const * _capturedExpression,
+                                    ResultDisposition::Flags _resultDisposition,
+                                    char const * _secondArg)
     :   macroName( _macroName ),
         lineInfo( _lineInfo ),
         capturedExpression( _capturedExpression ),
-        resultDisposition( _resultDisposition )
+        resultDisposition( _resultDisposition ),
+        secondArg( _secondArg )
     {}
 
     AssertionResult::AssertionResult() {}
@@ -7868,24 +8133,30 @@ namespace Catch {
     }
 
     bool AssertionResult::hasExpression() const {
-        return !m_info.capturedExpression.empty();
+        return m_info.capturedExpression[0] != 0;
     }
 
     bool AssertionResult::hasMessage() const {
         return !m_resultData.message.empty();
     }
 
+    std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) {
+        return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"')
+            ? capturedExpression
+            : std::string(capturedExpression) + ", " + secondArg;
+    }
+
     std::string AssertionResult::getExpression() const {
         if( isFalseTest( m_info.resultDisposition ) )
-            return '!' + m_info.capturedExpression;
+            return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
         else
-            return m_info.capturedExpression;
+            return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
     }
     std::string AssertionResult::getExpressionInMacro() const {
-        if( m_info.macroName.empty() )
-            return m_info.capturedExpression;
+        if( m_info.macroName[0] == 0 )
+            return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg);
         else
-            return m_info.macroName + "( " + m_info.capturedExpression + " )";
+            return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )";
     }
 
     bool AssertionResult::hasExpandedExpression() const {
@@ -7945,17 +8216,13 @@ namespace Catch {
     }
     inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
         if( isReservedTag( tag ) ) {
-            {
-                Colour colourGuard( Colour::Red );
-                Catch::cerr()
-                    << "Tag name [" << tag << "] not allowed.\n"
-                    << "Tag names starting with non alpha-numeric characters are reserved\n";
-            }
-            {
-                Colour colourGuard( Colour::FileName );
-                Catch::cerr() << _lineInfo << std::endl;
-            }
-            exit(1);
+            std::ostringstream ss;
+            ss << Colour(Colour::Red)
+               << "Tag name [" << tag << "] not allowed.\n"
+               << "Tag names starting with non alpha-numeric characters are reserved\n"
+               << Colour(Colour::FileName)
+               << _lineInfo << '\n';
+            throw std::runtime_error(ss.str());
         }
     }
 
@@ -8117,7 +8384,7 @@ namespace Catch {
         (   unsigned int _majorVersion,
             unsigned int _minorVersion,
             unsigned int _patchNumber,
-            std::string const& _branchName,
+            char const * const _branchName,
             unsigned int _buildNumber )
     :   majorVersion( _majorVersion ),
         minorVersion( _minorVersion ),
@@ -8130,15 +8397,18 @@ namespace Catch {
         os  << version.majorVersion << '.'
             << version.minorVersion << '.'
             << version.patchNumber;
-
-        if( !version.branchName.empty() ) {
-            os  << '-' << version.branchName
-                << '.' << version.buildNumber;
+        // branchName is never null -> 0th char is \0 if it is empty
+        if (version.branchName[0]) {
+            os << '-' << version.branchName
+               << '.' << version.buildNumber;
         }
         return os;
     }
 
-    Version libraryVersion( 1, 8, 1, "", 0 );
+    inline Version libraryVersion() {
+        static Version version( 1, 9, 7, "", 0 );
+        return version;
+    }
 
 }
 
@@ -8172,7 +8442,9 @@ namespace Catch {
     {}
 
     ScopedMessage::~ScopedMessage() {
-        getResultCapture().popScopedMessage( m_info );
+        if ( !std::uncaught_exception() ){
+            getResultCapture().popScopedMessage(m_info);
+        }
     }
 
 } // end namespace Catch
@@ -8320,21 +8592,21 @@ namespace Catch {
 
     namespace {
 #ifdef CATCH_PLATFORM_WINDOWS
-        uint64_t getCurrentTicks() {
-            static uint64_t hz=0, hzo=0;
+        UInt64 getCurrentTicks() {
+            static UInt64 hz=0, hzo=0;
             if (!hz) {
                 QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
                 QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
             }
-            uint64_t t;
+            UInt64 t;
             QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
             return ((t-hzo)*1000000)/hz;
         }
 #else
-        uint64_t getCurrentTicks() {
+        UInt64 getCurrentTicks() {
             timeval t;
             gettimeofday(&t,CATCH_NULL);
-            return static_cast<uint64_t>( t.tv_sec ) * 1000000ull + static_cast<uint64_t>( t.tv_usec );
+            return static_cast<UInt64>( t.tv_sec ) * 1000000ull + static_cast<UInt64>( t.tv_usec );
         }
 #endif
     }
@@ -8486,6 +8758,10 @@ namespace Catch {
         m_timer.start();
     }
 
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17
+#endif
     Section::~Section() {
         if( m_sectionIncluded ) {
             SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() );
@@ -8495,6 +8771,9 @@ namespace Catch {
                 getResultCapture().sectionEnded( endInfo );
         }
     }
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
 
     // This indicates whether the section should be executed or not
     Section::operator bool() const {
@@ -8567,6 +8846,9 @@ namespace Catch {
         // be strace, for example) in /proc/$PID/status, so just get it from
         // there instead.
         bool isDebuggerActive(){
+            // Libstdc++ has a bug, where std::ifstream sets errno to 0
+            // This way our users can properly assert over errno values
+            ErrnoGuard guard;
             std::ifstream in("/proc/self/status");
             for( std::string line; std::getline(in, line); ) {
                 static const int PREFIX_LEN = 11;
@@ -8807,7 +9089,7 @@ std::string toString( std::nullptr_t ) {
             return "nil";
         return "@" + toString([nsstring UTF8String]);
     }
-    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) {
+    std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) {
         if( !nsstring )
             return "nil";
         return "@" + toString([nsstring UTF8String]);
@@ -8824,21 +9106,28 @@ std::string toString( std::nullptr_t ) {
 
 namespace Catch {
 
-    std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) {
-        return secondArg.empty() || secondArg == "\"\""
-            ? capturedExpression
-            : capturedExpression + ", " + secondArg;
-    }
     ResultBuilder::ResultBuilder(   char const* macroName,
                                     SourceLineInfo const& lineInfo,
                                     char const* capturedExpression,
                                     ResultDisposition::Flags resultDisposition,
                                     char const* secondArg )
-    :   m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ),
+    :   m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ),
         m_shouldDebugBreak( false ),
-        m_shouldThrow( false )
+        m_shouldThrow( false ),
+        m_guardException( false ),
+        m_usedStream( false )
     {}
 
+    ResultBuilder::~ResultBuilder() {
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+        if ( m_guardException ) {
+            stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE";
+            captureResult( ResultWas::ThrewException );
+            getCurrentContext().getResultCapture()->exceptionEarlyReported();
+        }
+#endif
+    }
+
     ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) {
         m_data.resultType = result;
         return *this;
@@ -8849,13 +9138,25 @@ namespace Catch {
     }
 
     void ResultBuilder::endExpression( DecomposedExpression const& expr ) {
-        AssertionResult result = build( expr );
-        handleResult( result );
+        // Flip bool results if FalseTest flag is set
+        if( isFalseTest( m_assertionInfo.resultDisposition ) ) {
+            m_data.negate( expr.isBinaryExpression() );
+        }
+
+        getResultCapture().assertionRun();
+
+        if(getCurrentContext().getConfig()->includeSuccessfulResults() || m_data.resultType != ResultWas::Ok)
+        {
+            AssertionResult result = build( expr );
+            handleResult( result );
+        }
+        else
+            getResultCapture().assertionPassed();
     }
 
     void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
         m_assertionInfo.resultDisposition = resultDisposition;
-        m_stream.oss << Catch::translateActiveException();
+        stream().oss << Catch::translateActiveException();
         captureResult( ResultWas::ThrewException );
     }
 
@@ -8876,7 +9177,7 @@ namespace Catch {
         assert( !isFalseTest( m_assertionInfo.resultDisposition ) );
         AssertionResultData data = m_data;
         data.resultType = ResultWas::Ok;
-        data.reconstructedExpression = m_assertionInfo.capturedExpression;
+        data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg);
 
         std::string actualMessage = Catch::translateActiveException();
         if( !matcher.match( actualMessage ) ) {
@@ -8937,18 +9238,21 @@ namespace Catch {
         assert( m_data.resultType != ResultWas::Unknown );
         AssertionResultData data = m_data;
 
-        // Flip bool results if FalseTest flag is set
-        if( isFalseTest( m_assertionInfo.resultDisposition ) ) {
-            data.negate( expr.isBinaryExpression() );
-        }
-
-        data.message = m_stream.oss.str();
+        if(m_usedStream)
+            data.message = m_stream().oss.str();
         data.decomposedExpression = &expr; // for lazy reconstruction
         return AssertionResult( m_assertionInfo, data );
     }
 
     void ResultBuilder::reconstructExpression( std::string& dest ) const {
-        dest = m_assertionInfo.capturedExpression;
+        dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg);
+    }
+
+    void ResultBuilder::setExceptionGuard() {
+        m_guardException = true;
+    }
+    void ResultBuilder::unsetExceptionGuard() {
+        m_guardException = false;
     }
 
 } // end namespace Catch
@@ -8956,27 +9260,6 @@ namespace Catch {
 // #included from: catch_tag_alias_registry.hpp
 #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED
 
-// #included from: catch_tag_alias_registry.h
-#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
-
-#include <map>
-
-namespace Catch {
-
-    class TagAliasRegistry : public ITagAliasRegistry {
-    public:
-        virtual ~TagAliasRegistry();
-        virtual Option<TagAlias> find( std::string const& alias ) const;
-        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
-        void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
-        static TagAliasRegistry& get();
-
-    private:
-        std::map<std::string, TagAlias> m_registry;
-    };
-
-} // end namespace Catch
-
 namespace Catch {
 
     TagAliasRegistry::~TagAliasRegistry() {}
@@ -9004,40 +9287,36 @@ namespace Catch {
         return expandedTestSpec;
     }
 
-    void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {
 
         if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) {
             std::ostringstream oss;
-            oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo;
+            oss << Colour( Colour::Red )
+                << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n"
+                << Colour( Colour::FileName )
+                << lineInfo << '\n';
             throw std::domain_error( oss.str().c_str() );
         }
         if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
             std::ostringstream oss;
-            oss << "error: tag alias, \"" << alias << "\" already registered.\n"
-                << "\tFirst seen at " << find(alias)->lineInfo << '\n'
-                << "\tRedefined at " << lineInfo;
+            oss << Colour( Colour::Red )
+                << "error: tag alias, \"" << alias << "\" already registered.\n"
+                << "\tFirst seen at "
+                << Colour( Colour::Red ) << find(alias)->lineInfo << '\n'
+                << Colour( Colour::Red ) << "\tRedefined at "
+                << Colour( Colour::FileName) << lineInfo << '\n';
             throw std::domain_error( oss.str().c_str() );
         }
     }
 
-    TagAliasRegistry& TagAliasRegistry::get() {
-        static TagAliasRegistry instance;
-        return instance;
+    ITagAliasRegistry::~ITagAliasRegistry() {}
 
+    ITagAliasRegistry const& ITagAliasRegistry::get() {
+        return getRegistryHub().getTagAliasRegistry();
     }
 
-    ITagAliasRegistry::~ITagAliasRegistry() {}
-    ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); }
-
     RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
-        try {
-            TagAliasRegistry::get().add( alias, tag, lineInfo );
-        }
-        catch( std::exception& ex ) {
-            Colour colourGuard( Colour::Red );
-            Catch::cerr() << ex.what() << std::endl;
-            exit(1);
-        }
+        getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo );
     }
 
 } // end namespace Catch
@@ -9064,7 +9343,7 @@ namespace Matchers {
                    : std::string();
         }
 
-        StringMatcherBase::StringMatcherBase( std::string operation, CasedString const& comparator )
+        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )
         : m_comparator( comparator ),
           m_operation( operation ) {
         }
@@ -9265,10 +9544,34 @@ Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingRepo
 #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
 
 #include <cstring>
+#include <cfloat>
+#include <cstdio>
 #include <assert.h>
 
 namespace Catch {
 
+    namespace {
+        // Because formatting using c++ streams is stateful, drop down to C is required
+        // Alternatively we could use stringstream, but its performance is... not good.
+        std::string getFormattedDuration( double duration ) {
+            // Max exponent + 1 is required to represent the whole part
+            // + 1 for decimal point
+            // + 3 for the 3 decimal places
+            // + 1 for null terminator
+            const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
+            char buffer[maxDoubleSize];
+
+            // Save previous errno, to prevent sprintf from overwriting it
+            ErrnoGuard guard;
+#ifdef _MSC_VER
+            sprintf_s(buffer, "%.3f", duration);
+#else
+            sprintf(buffer, "%.3f", duration);
+#endif
+            return std::string(buffer);
+        }
+    }
+
     struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
 
         StreamingReporterBase( ReporterConfig const& _config )
@@ -9365,7 +9668,8 @@ namespace Catch {
             BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
             BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
             bool operator() ( Ptr<SectionNode> const& node ) const {
-                return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
+                return ((node->stats.sectionInfo.name == m_other.name) &&
+                        (node->stats.sectionInfo.lineInfo == m_other.lineInfo));
             }
         private:
             void operator=( BySectionInfo const& );
@@ -9594,9 +9898,13 @@ namespace Catch {
 #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \
     namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
 
+// Deprecated - use the form without INTERNAL_
 #define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \
     namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
 
+#define CATCH_REGISTER_LISTENER( listenerType ) \
+    namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
+
 // #included from: ../internal/catch_xmlwriter.hpp
 #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 
@@ -9829,20 +10137,6 @@ namespace Catch {
     };
 
 }
-// #included from: catch_reenable_warnings.h
-
-#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
-
-#ifdef __clang__
-#    ifdef __ICC // icpc defines the __clang__ macro
-#        pragma warning(pop)
-#    else
-#        pragma clang diagnostic pop
-#    endif
-#elif defined __GNUC__
-#    pragma GCC diagnostic pop
-#endif
-
 
 namespace Catch {
     class XmlReporter : public StreamingReporterBase {
@@ -9921,73 +10215,76 @@ namespace Catch {
         virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { }
 
         virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
-            const AssertionResult& assertionResult = assertionStats.assertionResult;
 
-            // Print any info messages in <Info> tags.
-            if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+            AssertionResult const& result = assertionStats.assertionResult;
+
+            bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
+
+            if( includeResults ) {
+                // Print any info messages in <Info> tags.
                 for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
-                        it != itEnd;
-                        ++it ) {
+                     it != itEnd;
+                     ++it ) {
                     if( it->type == ResultWas::Info ) {
                         m_xml.scopedElement( "Info" )
-                            .writeText( it->message );
+                                .writeText( it->message );
                     } else if ( it->type == ResultWas::Warning ) {
                         m_xml.scopedElement( "Warning" )
-                            .writeText( it->message );
+                                .writeText( it->message );
                     }
                 }
             }
 
             // Drop out if result was successful but we're not printing them.
-            if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+            if( !includeResults && result.getResultType() != ResultWas::Warning )
                 return true;
 
             // Print the expression if there is one.
-            if( assertionResult.hasExpression() ) {
+            if( result.hasExpression() ) {
                 m_xml.startElement( "Expression" )
-                    .writeAttribute( "success", assertionResult.succeeded() )
-                    .writeAttribute( "type", assertionResult.getTestMacroName() );
+                    .writeAttribute( "success", result.succeeded() )
+                    .writeAttribute( "type", result.getTestMacroName() );
 
-                writeSourceInfo( assertionResult.getSourceInfo() );
+                writeSourceInfo( result.getSourceInfo() );
 
                 m_xml.scopedElement( "Original" )
-                    .writeText( assertionResult.getExpression() );
+                    .writeText( result.getExpression() );
                 m_xml.scopedElement( "Expanded" )
-                    .writeText( assertionResult.getExpandedExpression() );
+                    .writeText( result.getExpandedExpression() );
             }
 
             // And... Print a result applicable to each result type.
-            switch( assertionResult.getResultType() ) {
+            switch( result.getResultType() ) {
                 case ResultWas::ThrewException:
                     m_xml.startElement( "Exception" );
-                    writeSourceInfo( assertionResult.getSourceInfo() );
-                    m_xml.writeText( assertionResult.getMessage() );
+                    writeSourceInfo( result.getSourceInfo() );
+                    m_xml.writeText( result.getMessage() );
                     m_xml.endElement();
                     break;
                 case ResultWas::FatalErrorCondition:
                     m_xml.startElement( "FatalErrorCondition" );
-                    writeSourceInfo( assertionResult.getSourceInfo() );
-                    m_xml.writeText( assertionResult.getMessage() );
+                    writeSourceInfo( result.getSourceInfo() );
+                    m_xml.writeText( result.getMessage() );
                     m_xml.endElement();
                     break;
                 case ResultWas::Info:
                     m_xml.scopedElement( "Info" )
-                        .writeText( assertionResult.getMessage() );
+                        .writeText( result.getMessage() );
                     break;
                 case ResultWas::Warning:
                     // Warning will already have been written
                     break;
                 case ResultWas::ExplicitFailure:
                     m_xml.startElement( "Failure" );
-                    writeSourceInfo( assertionResult.getSourceInfo() );
-                    m_xml.writeText( assertionResult.getMessage() );
+                    writeSourceInfo( result.getSourceInfo() );
+                    m_xml.writeText( result.getMessage() );
                     m_xml.endElement();
                     break;
                 default:
                     break;
             }
 
-            if( assertionResult.hasExpression() )
+            if( result.hasExpression() )
                 m_xml.endElement();
 
             return true;
@@ -10093,7 +10390,9 @@ namespace Catch {
     public:
         JunitReporter( ReporterConfig const& _config )
         :   CumulativeReporterBase( _config ),
-            xml( _config.stream() )
+            xml( _config.stream() ),
+            unexpectedExceptions( 0 ),
+            m_okToFail( false )
         {
             m_reporterPrefs.shouldRedirectStdOut = true;
         }
@@ -10119,8 +10418,11 @@ namespace Catch {
             CumulativeReporterBase::testGroupStarting( groupInfo );
         }
 
+        virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE {
+            m_okToFail = testCaseInfo.okToFail();
+        }
         virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
-            if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
+            if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
                 unexpectedExceptions++;
             return CumulativeReporterBase::assertionEnded( assertionStats );
         }
@@ -10285,6 +10587,7 @@ namespace Catch {
         std::ostringstream stdOutForSuite;
         std::ostringstream stdErrForSuite;
         unsigned int unexpectedExceptions;
+        bool m_okToFail;
     };
 
     INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
@@ -10299,25 +10602,6 @@ namespace Catch {
 
 namespace Catch {
 
-    namespace {
-        // Because formatting using c++ streams is stateful, drop down to C is required
-        // Alternatively we could use stringstream, but its performance is... not good.
-        std::string getFormattedDuration( double duration ) {
-            // Max exponent + 1 is required to represent the whole part
-            // + 1 for decimal point
-            // + 3 for the 3 decimal places
-            // + 1 for null terminator
-            const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
-            char buffer[maxDoubleSize];
-#ifdef _MSC_VER
-            sprintf_s(buffer, "%.3f", duration);
-#else
-            sprintf(buffer, "%.3f", duration);
-#endif
-            return std::string(buffer);
-        }
-    }
-
     struct ConsoleReporter : StreamingReporterBase {
         ConsoleReporter( ReporterConfig const& _config )
         :   StreamingReporterBase( _config ),
@@ -10339,18 +10623,15 @@ namespace Catch {
         virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE {
             AssertionResult const& result = _assertionStats.assertionResult;
 
-            bool printInfoMessages = true;
+            bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
 
-            // Drop out if result was successful and we're not printing those
-            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
-                if( result.getResultType() != ResultWas::Warning )
-                    return false;
-                printInfoMessages = false;
-            }
+            // Drop out if result was successful but we're not printing them.
+            if( !includeResults && result.getResultType() != ResultWas::Warning )
+                return false;
 
             lazyPrint();
 
-            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+            AssertionPrinter printer( stream, _assertionStats, includeResults );
             printer.print();
             stream << std::endl;
             return true;
@@ -10440,7 +10721,11 @@ namespace Catch {
                     case ResultWas::ThrewException:
                         colour = Colour::Error;
                         passOrFail = "FAILED";
-                        messageLabel = "due to unexpected exception with message";
+                        messageLabel = "due to unexpected exception with ";
+                        if (_stats.infoMessages.size() == 1)
+                            messageLabel += "message";
+                        if (_stats.infoMessages.size() > 1)
+                            messageLabel += "messages";
                         break;
                     case ResultWas::FatalErrorCondition:
                         colour = Colour::Error;
@@ -10556,7 +10841,7 @@ namespace Catch {
             stream  << '\n' << getLineOfChars<'~'>() << '\n';
             Colour colour( Colour::SecondaryText );
             stream  << currentTestRunInfo->name
-                    << " is a Catch v"  << libraryVersion << " host application.\n"
+                    << " is a Catch v"  << libraryVersion() << " host application.\n"
                     << "Run with -? for options\n\n";
 
             if( m_config->rngSeed() != 0 )
@@ -10769,8 +11054,7 @@ namespace Catch {
             stream << "No test cases matched '" << spec << '\'' << std::endl;
         }
 
-        virtual void assertionStarting( AssertionInfo const& ) {
-        }
+        virtual void assertionStarting( AssertionInfo const& ) {}
 
         virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
             AssertionResult const& result = _assertionStats.assertionResult;
@@ -10791,6 +11075,12 @@ namespace Catch {
             return true;
         }
 
+        virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE {
+            if (m_config->showDurations() == ShowDurations::Always) {
+                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+            }
+        }
+
         virtual void testRunEnded( TestRunStats const& _testRunStats ) {
             printTotals( _testRunStats.totals );
             stream << '\n' << std::endl;
@@ -10896,7 +11186,7 @@ namespace Catch {
                 stream << result.getSourceInfo() << ':';
             }
 
-            void printResultType( Colour::Code colour, std::string passOrFail ) const {
+            void printResultType( Colour::Code colour, std::string const& passOrFail ) const {
                 if( !passOrFail.empty() ) {
                     {
                         Colour colourGuard( colour );
@@ -10906,7 +11196,7 @@ namespace Catch {
                 }
             }
 
-            void printIssue( std::string issue ) const {
+            void printIssue( std::string const& issue ) const {
                 stream << ' ' << issue;
             }
 
@@ -11077,6 +11367,7 @@ namespace Catch {
     TestSpec::NamePattern::~NamePattern() {}
     TestSpec::TagPattern::~TagPattern() {}
     TestSpec::ExcludedPattern::~ExcludedPattern() {}
+    Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {}
 
     void Config::dummy() {}
 
@@ -11100,9 +11391,15 @@ namespace Catch {
 
 #ifndef __OBJC__
 
+#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
+// Standard C/C++ Win32 Unicode wmain entry point
+extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
+#else
 // Standard C/C++ main entry point
 int main (int argc, char * argv[]) {
-	int result = Catch::Session().run( argc, argv );
+#endif
+
+    int result = Catch::Session().run( argc, argv );
     return ( result < 0xff ? result : 0xff );
 }
 
@@ -11137,33 +11434,43 @@ int main (int argc, char * const argv[]) {
 // If this config identifier is defined then all CATCH macros are prefixed with CATCH_
 #ifdef CATCH_CONFIG_PREFIX_ALL
 
-#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" )
-#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
+#else
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr  )
+#endif
 
-#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" )
-#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" )
-#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" )
-#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" )
+#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr )
 
-#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" )
-#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" )
-#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" )
-#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" )
-#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" )
+#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr )
+#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr )
 
-#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CATCH_CHECK_THROWS" )
-#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" )
-#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" )
-#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" )
+#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr )
 
-#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
-#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" )
+#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
 
-#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
-#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg )
-#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
-#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
-#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#else
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
+#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) )
+#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) )
 
 #ifdef CATCH_CONFIG_VARIADIC_MACROS
     #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
@@ -11171,16 +11478,18 @@ int main (int argc, char * const argv[]) {
     #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
     #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
     #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
-    #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ )
-    #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ )
+    #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+    #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+    #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 #else
     #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
     #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
     #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
     #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description )
     #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
-    #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg )
-    #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg )
+    #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg )
+    #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg )
+    #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg )
 #endif
 #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
 
@@ -11206,50 +11515,63 @@ int main (int argc, char * const argv[]) {
 // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
 #else
 
-#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" )
-#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
+
+#else
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr  )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr )
+#endif
+
+#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr )
 
-#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" )
-#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" )
-#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" )
-#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" )
+#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr )
+#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr )
 
-#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" )
-#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" )
-#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" )
-#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" )
-#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" )
+#define CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr )
 
-#define CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" )
-#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" )
-#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" )
-#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" )
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
 
-#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" )
-#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" )
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#else
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif
 
-#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
-#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg )
-#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
-#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
-#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
+#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
+#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) )
+#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) )
 
 #ifdef CATCH_CONFIG_VARIADIC_MACROS
-    #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
-    #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
-    #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
-    #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
-    #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
-    #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ )
-    #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ )
+#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
 #else
-    #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
     #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
     #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
     #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description )
     #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
-    #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg )
-    #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg )
+    #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg )
+    #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg )
+    #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg )
 #endif
 #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
 
@@ -11278,5 +11600,19 @@ int main (int argc, char * const argv[]) {
 
 using Catch::Detail::Approx;
 
+// #included from: internal/catch_reenable_warnings.h
+
+#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
+
+#ifdef __clang__
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
+#elif defined __GNUC__
+#    pragma GCC diagnostic pop
+#endif
+
 #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
 
diff --git a/test/t/area/test_node_ref_segment.cpp b/test/t/area/test_node_ref_segment.cpp
index 45e6f9d..3b47376 100644
--- a/test/t/area/test_node_ref_segment.cpp
+++ b/test/t/area/test_node_ref_segment.cpp
@@ -135,11 +135,11 @@ TEST_CASE("Ordering of NodeRefSegements") {
     REQUIRE(node_ref1 < node_ref3);
     REQUIRE(node_ref1 >= node_ref1);
 
-    REQUIRE( osmium::location_less()(node_ref1, node_ref2));
-    REQUIRE(!osmium::location_less()(node_ref2, node_ref3));
-    REQUIRE( osmium::location_less()(node_ref1, node_ref3));
-    REQUIRE( osmium::location_less()(node_ref3, node_ref4));
-    REQUIRE(!osmium::location_less()(node_ref1, node_ref1));
+    REQUIRE(      osmium::location_less()(node_ref1, node_ref2));
+    REQUIRE_FALSE(osmium::location_less()(node_ref2, node_ref3));
+    REQUIRE(      osmium::location_less()(node_ref1, node_ref3));
+    REQUIRE(      osmium::location_less()(node_ref3, node_ref4));
+    REQUIRE_FALSE(osmium::location_less()(node_ref1, node_ref1));
 }
 
 TEST_CASE("More ordering of NodeRefSegments") {
diff --git a/test/t/geom/test_geojson.cpp b/test/t/geom/test_geojson.cpp
index 5724936..400bb50 100644
--- a/test/t/geom/test_geojson.cpp
+++ b/test/t/geom/test_geojson.cpp
@@ -14,7 +14,7 @@ TEST_CASE("GeoJSON point geometry") {
     }
 
     SECTION("empty_point") {
-        REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location);
+        REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), const osmium::invalid_location&);
     }
 
 }
@@ -50,17 +50,17 @@ TEST_CASE("GeoJSON linestring geometry") {
     SECTION("empty_linestring") {
         const auto& wnl = create_test_wnl_empty(buffer);
 
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), const osmium::geometry_error&);
     }
 
     SECTION("linestring with two same locations") {
         const auto& wnl = create_test_wnl_same_location(buffer);
 
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), const osmium::geometry_error&);
 
         {
             const std::string json{factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
@@ -75,7 +75,7 @@ TEST_CASE("GeoJSON linestring geometry") {
 
     SECTION("linestring with undefined location") {
         const auto& wnl = create_test_wnl_undefined_location(buffer);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::invalid_location&);
     }
 
 }
@@ -87,7 +87,7 @@ TEST_CASE("GeoJSON area geometry") {
     SECTION("area_1outer_0inner") {
         const osmium::Area& area = create_test_area_1outer_0inner(buffer);
 
-        REQUIRE(!area.is_multipolygon());
+        REQUIRE_FALSE(area.is_multipolygon());
         REQUIRE(std::distance(area.cbegin(), area.cend()) == 2);
         REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
 
@@ -98,7 +98,7 @@ TEST_CASE("GeoJSON area geometry") {
     SECTION("area_1outer_1inner") {
         const osmium::Area& area = create_test_area_1outer_1inner(buffer);
 
-        REQUIRE(!area.is_multipolygon());
+        REQUIRE_FALSE(area.is_multipolygon());
         REQUIRE(std::distance(area.cbegin(), area.cend()) == 3);
         REQUIRE(area.subitems<osmium::OuterRing>().size() == area.num_rings().first);
         REQUIRE(area.subitems<osmium::InnerRing>().size() == area.num_rings().second);
diff --git a/test/t/geom/test_geos.cpp b/test/t/geom/test_geos.cpp
index 8e7fac4..f138616 100644
--- a/test/t/geom/test_geos.cpp
+++ b/test/t/geom/test_geos.cpp
@@ -41,7 +41,7 @@ TEST_CASE("GEOS geometry factory - create point with externally created GEOS fac
 TEST_CASE("GEOS geometry factory - can not create from invalid location") {
     osmium::geom::GEOSFactory<> factory;
 
-    REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location);
+    REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), const osmium::invalid_location&);
 }
 
 TEST_CASE("GEOS geometry factory - create linestring") {
diff --git a/test/t/geom/test_mercator.cpp b/test/t/geom/test_mercator.cpp
index cc16e55..4f8bf97 100644
--- a/test/t/geom/test_mercator.cpp
+++ b/test/t/geom/test_mercator.cpp
@@ -2,36 +2,33 @@
 
 #include <osmium/geom/mercator_projection.hpp>
 
-TEST_CASE("Mercator") {
-
-    SECTION("mercator_projection") {
-        osmium::geom::MercatorProjection projection;
-        REQUIRE(3857 == projection.epsg());
-        REQUIRE("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs" == projection.proj_string());
-    }
-
-    SECTION("low_level_mercator_functions") {
-        osmium::geom::Coordinates c1(17.839, -3.249);
-        osmium::geom::Coordinates r1 = osmium::geom::mercator_to_lonlat(osmium::geom::lonlat_to_mercator(c1));
-        REQUIRE(r1.x == Approx(c1.x).epsilon(0.000001));
-        REQUIRE(r1.y == Approx(c1.y).epsilon(0.000001));
-
-        osmium::geom::Coordinates c2(-89.2, 15.915);
-        osmium::geom::Coordinates r2 = osmium::geom::mercator_to_lonlat(osmium::geom::lonlat_to_mercator(c2));
-        REQUIRE(r2.x == Approx(c2.x).epsilon(0.000001));
-        REQUIRE(r2.y == Approx(c2.y).epsilon(0.000001));
-
-        osmium::geom::Coordinates c3(180.0, 85.0);
-        osmium::geom::Coordinates r3 = osmium::geom::mercator_to_lonlat(osmium::geom::lonlat_to_mercator(c3));
-        REQUIRE(r3.x == Approx(c3.x).epsilon(0.000001));
-        REQUIRE(r3.y == Approx(c3.y).epsilon(0.000001));
-    }
+TEST_CASE("Mercator projection") {
+    const osmium::geom::MercatorProjection projection;
+    REQUIRE(3857 == projection.epsg());
+    REQUIRE("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs" == projection.proj_string());
+}
 
-    SECTION("mercator_bounds") {
-        osmium::Location mmax(180.0, osmium::geom::MERCATOR_MAX_LAT);
-        osmium::geom::Coordinates c = osmium::geom::lonlat_to_mercator(mmax);
-        REQUIRE(c.x == Approx(c.y).epsilon(0.001));
-        REQUIRE(osmium::geom::detail::y_to_lat(osmium::geom::detail::lon_to_x(180.0)) == Approx(osmium::geom::MERCATOR_MAX_LAT).epsilon(0.0000001));
-    }
+TEST_CASE("Low level mercator functions") {
+    const osmium::geom::Coordinates c1{17.839, -3.249};
+    const osmium::geom::Coordinates r1 = osmium::geom::mercator_to_lonlat(osmium::geom::lonlat_to_mercator(c1));
+    REQUIRE(r1.x == Approx(c1.x).epsilon(0.000001));
+    REQUIRE(r1.y == Approx(c1.y).epsilon(0.000001));
+
+    const osmium::geom::Coordinates c2{-89.2, 15.915};
+    const osmium::geom::Coordinates r2 = osmium::geom::mercator_to_lonlat(osmium::geom::lonlat_to_mercator(c2));
+    REQUIRE(r2.x == Approx(c2.x).epsilon(0.000001));
+    REQUIRE(r2.y == Approx(c2.y).epsilon(0.000001));
+
+    const osmium::geom::Coordinates c3{180.0, 85.0};
+    const osmium::geom::Coordinates r3 = osmium::geom::mercator_to_lonlat(osmium::geom::lonlat_to_mercator(c3));
+    REQUIRE(r3.x == Approx(c3.x).epsilon(0.000001));
+    REQUIRE(r3.y == Approx(c3.y).epsilon(0.000001));
+}
 
+TEST_CASE("Mercator bounds") {
+    const osmium::Location mmax{180.0, osmium::geom::MERCATOR_MAX_LAT};
+    const osmium::geom::Coordinates c = osmium::geom::lonlat_to_mercator(mmax);
+    REQUIRE(c.x == Approx(c.y).epsilon(0.001));
+    REQUIRE(osmium::geom::detail::y_to_lat(osmium::geom::detail::lon_to_x(180.0)) == Approx(osmium::geom::MERCATOR_MAX_LAT).epsilon(0.0000001));
 }
+
diff --git a/test/t/geom/test_ogr.cpp b/test/t/geom/test_ogr.cpp
index 5e03082..39f2b5c 100644
--- a/test/t/geom/test_ogr.cpp
+++ b/test/t/geom/test_ogr.cpp
@@ -15,7 +15,7 @@ TEST_CASE("OGR point geometry") {
     }
 
     SECTION("empty_point") {
-        REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
+        REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), const osmium::invalid_location&);
     }
 
 }
diff --git a/test/t/geom/test_projection.cpp b/test/t/geom/test_projection.cpp
index df5c095..bff568b 100644
--- a/test/t/geom/test_projection.cpp
+++ b/test/t/geom/test_projection.cpp
@@ -35,11 +35,11 @@ TEST_CASE("Projection 4326 from init string") {
 }
 
 TEST_CASE("Creating projection from unknown init string") {
-    REQUIRE_THROWS_AS(osmium::geom::Projection{"abc"}, osmium::projection_error);
+    REQUIRE_THROWS_AS(osmium::geom::Projection{"abc"}, const osmium::projection_error&);
 }
 
 TEST_CASE("Creating projection from unknown EPSG code") {
-    REQUIRE_THROWS_AS(osmium::geom::Projection{9999999}, osmium::projection_error);
+    REQUIRE_THROWS_AS(osmium::geom::Projection{9999999}, const osmium::projection_error&);
 }
 
 TEST_CASE("Projection 3857") {
diff --git a/test/t/geom/test_tile.cpp b/test/t/geom/test_tile.cpp
index 4218797..ca289f7 100644
--- a/test/t/geom/test_tile.cpp
+++ b/test/t/geom/test_tile.cpp
@@ -52,7 +52,7 @@ TEST_CASE("Tile from x0.0 y0.0 at zoom 4") {
 
     osmium::geom::Tile t{4, l};
 
-    auto n = 1 << (4-1);
+    const auto n = 1 << (4-1);
     REQUIRE(t.x == n);
     REQUIRE(t.y == n);
     REQUIRE(t.z == 4);
@@ -115,3 +115,29 @@ TEST_CASE("Check a random list of tiles") {
     }
 }
 
+TEST_CASE("Invalid tiles") {
+    osmium::geom::Tile tile{0, 0, 0};
+
+    REQUIRE(tile.valid());
+
+    SECTION("Zoom level out of bounds") {
+        tile.z = 100;
+    }
+    SECTION("x out of bounds") {
+        tile.x = 1;
+    }
+    SECTION("y out of bounds") {
+        tile.y = 1;
+    }
+    SECTION("x out of bounds") {
+        tile.z = 4;
+        tile.x = 100;
+    }
+    SECTION("y out of bounds") {
+        tile.z = 4;
+        tile.y = 100;
+    }
+
+    REQUIRE_FALSE(tile.valid());
+}
+
diff --git a/test/t/geom/test_wkb.cpp b/test/t/geom/test_wkb.cpp
index 29b6d6b..a4f2d99 100644
--- a/test/t/geom/test_wkb.cpp
+++ b/test/t/geom/test_wkb.cpp
@@ -87,11 +87,11 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
         const auto& wnl = create_test_wnl_same_location(buffer);
 
         SECTION("unique forwards (default)") {
-            REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::geometry_error&);
         }
 
         SECTION("unique backwards") {
-            REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), const osmium::geometry_error&);
         }
 
         SECTION("all forwards") {
@@ -110,7 +110,7 @@ TEST_CASE("WKB geometry factory (byte-order-dependant)") {
 
         const auto& wnl = create_test_wnl_undefined_location(buffer);
 
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::invalid_location&);
     }
 
 }
@@ -122,17 +122,17 @@ TEST_CASE("WKB geometry (byte-order-independent)") {
     osmium::geom::WKBFactory<> factory{osmium::geom::wkb_type::wkb, osmium::geom::out_type::hex};
 
     SECTION("empty point") {
-        REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), osmium::invalid_location);
+        REQUIRE_THROWS_AS(factory.create_point(osmium::Location{}), const osmium::invalid_location&);
     }
 
     SECTION("empty linestring") {
         osmium::memory::Buffer buffer{10000};
         const auto& wnl = create_test_wnl_empty(buffer);
 
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), const osmium::geometry_error&);
     }
 
 }
diff --git a/test/t/geom/test_wkt.cpp b/test/t/geom/test_wkt.cpp
index f6913c4..7f33868 100644
--- a/test/t/geom/test_wkt.cpp
+++ b/test/t/geom/test_wkt.cpp
@@ -7,29 +7,25 @@
 #include "wnl_helper.hpp"
 
 TEST_CASE("WKT geometry for point") {
+    const osmium::geom::WKTFactory<> factory;
+    const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
+    REQUIRE(wkt == "POINT(3.2 4.2)");
+}
 
-    osmium::geom::WKTFactory<> factory;
-
-    SECTION("point") {
-        const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
-        REQUIRE(wkt == "POINT(3.2 4.2)");
-    }
-
-    SECTION("empty point") {
-        REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
-    }
-
+TEST_CASE("WKT geometry for empty point") {
+    const osmium::geom::WKTFactory<> factory;
+    REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), const osmium::invalid_location&);
 }
 
 TEST_CASE("WKT geometry for point in ekwt") {
-    osmium::geom::WKTFactory<> factory(7, osmium::geom::wkt_type::ewkt);
+    const osmium::geom::WKTFactory<> factory{7, osmium::geom::wkt_type::ewkt};
 
     const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
     REQUIRE(wkt == "SRID=4326;POINT(3.2 4.2)");
 }
 
 TEST_CASE("WKT geometry for point in ekwt in web mercator") {
-    osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory(2, osmium::geom::wkt_type::ewkt);
+    const osmium::geom::WKTFactory<osmium::geom::MercatorProjection> factory{2, osmium::geom::wkt_type::ewkt};
 
     const std::string wkt{factory.create_point(osmium::Location{3.2, 4.2})};
     REQUIRE(wkt == "SRID=3857;POINT(356222.37 467961.14)");
@@ -67,17 +63,17 @@ TEST_CASE("WKT geometry factory") {
     SECTION("empty linestring") {
         const auto& wnl = create_test_wnl_empty(buffer);
 
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), osmium::geometry_error);
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), osmium::geometry_error);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all), const osmium::geometry_error&);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward), const osmium::geometry_error&);
     }
 
     SECTION("linestring with two same locations") {
         const auto& wnl = create_test_wnl_same_location(buffer);
 
         SECTION("unique forwards") {
-            REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::geometry_error);
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::geometry_error&);
 
             try {
                 factory.create_linestring(wnl);
@@ -88,7 +84,7 @@ TEST_CASE("WKT geometry factory") {
         }
 
         SECTION("unique backwards") {
-            REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), osmium::geometry_error);
+            REQUIRE_THROWS_AS(factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward), const osmium::geometry_error&);
         }
 
         SECTION("all forwards") {
@@ -105,7 +101,7 @@ TEST_CASE("WKT geometry factory") {
     SECTION("linestring with undefined location") {
         const auto& wnl = create_test_wnl_undefined_location(buffer);
 
-        REQUIRE_THROWS_AS(factory.create_linestring(wnl), osmium::invalid_location);
+        REQUIRE_THROWS_AS(factory.create_linestring(wnl), const osmium::invalid_location&);
     }
 
     SECTION("area with one outer and no inner rings") {
diff --git a/test/t/handler/test_check_order_handler.cpp b/test/t/handler/test_check_order_handler.cpp
new file mode 100644
index 0000000..e6b1600
--- /dev/null
+++ b/test/t/handler/test_check_order_handler.cpp
@@ -0,0 +1,128 @@
+#include "catch.hpp"
+
+#include <osmium/handler/check_order.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/opl.hpp>
+#include <osmium/visitor.hpp>
+
+TEST_CASE("CheckOrder handler if everything is in order") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("n-126", buffer));
+    REQUIRE(osmium::opl_parse("n123", buffer));
+    REQUIRE(osmium::opl_parse("n124", buffer));
+    REQUIRE(osmium::opl_parse("n128", buffer));
+    REQUIRE(osmium::opl_parse("w-100", buffer));
+    REQUIRE(osmium::opl_parse("w100", buffer));
+    REQUIRE(osmium::opl_parse("w102", buffer));
+    REQUIRE(osmium::opl_parse("r-200", buffer));
+    REQUIRE(osmium::opl_parse("r100", buffer));
+
+    osmium::handler::CheckOrder handler;
+    osmium::apply(buffer, handler);
+    REQUIRE(handler.max_node_id()     == 128);
+    REQUIRE(handler.max_way_id()      == 102);
+    REQUIRE(handler.max_relation_id() == 100);
+}
+
+TEST_CASE("CheckOrder handler: Nodes must be in order") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("n3", buffer));
+
+    SECTION("Positive ID") {
+        REQUIRE(osmium::opl_parse("n2", buffer));
+    }
+    SECTION("Negative ID") {
+        REQUIRE(osmium::opl_parse("n-2", buffer));
+    }
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
+TEST_CASE("CheckOrder handler: Ways must be in order") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("w3", buffer));
+    SECTION("Positive ID") {
+        REQUIRE(osmium::opl_parse("w2", buffer));
+    }
+    SECTION("Negative ID") {
+        REQUIRE(osmium::opl_parse("w-2", buffer));
+    }
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
+TEST_CASE("CheckOrder handler: Relations must be in order") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("r3", buffer));
+    SECTION("Positive ID") {
+        REQUIRE(osmium::opl_parse("r2", buffer));
+    }
+    SECTION("Negative ID") {
+        REQUIRE(osmium::opl_parse("r-2", buffer));
+    }
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
+TEST_CASE("CheckOrder handler: Same id twice is not allowed") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("n3", buffer));
+    REQUIRE(osmium::opl_parse("n3", buffer));
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
+TEST_CASE("CheckOrder handler: Nodes after ways") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("w50", buffer));
+    SECTION("Positive ID") {
+        REQUIRE(osmium::opl_parse("n30", buffer));
+    }
+    SECTION("Negative ID") {
+        REQUIRE(osmium::opl_parse("n-30", buffer));
+    }
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
+TEST_CASE("CheckOrder handler: Nodes after relations") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("r50", buffer));
+    SECTION("Positive ID") {
+        REQUIRE(osmium::opl_parse("n30", buffer));
+    }
+    SECTION("Negative ID") {
+        REQUIRE(osmium::opl_parse("n-30", buffer));
+    }
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
+TEST_CASE("CheckOrder handler: Ways after relations") {
+    osmium::memory::Buffer buffer{1024};
+
+    REQUIRE(osmium::opl_parse("r50", buffer));
+    SECTION("Positive ID") {
+        REQUIRE(osmium::opl_parse("w30", buffer));
+    }
+    SECTION("Negative ID") {
+        REQUIRE(osmium::opl_parse("w-30", buffer));
+    }
+
+    osmium::handler::CheckOrder handler;
+    REQUIRE_THROWS_AS(osmium::apply(buffer, handler), const osmium::out_of_order_error&);
+}
+
diff --git a/test/t/handler/test_dynamic_handler.cpp b/test/t/handler/test_dynamic_handler.cpp
new file mode 100644
index 0000000..7b20f12
--- /dev/null
+++ b/test/t/handler/test_dynamic_handler.cpp
@@ -0,0 +1,116 @@
+#include "catch.hpp"
+
+#include <osmium/dynamic_handler.hpp>
+#include <osmium/builder/attr.hpp>
+#include <osmium/visitor.hpp>
+
+struct Handler1 : public osmium::handler::Handler {
+
+    int& count;
+
+    explicit Handler1(int& c) :
+        count(c) {
+    }
+
+    void node(const osmium::Node&) noexcept {
+        ++count;
+    }
+
+    void way(const osmium::Way&) noexcept {
+        ++count;
+    }
+
+    void relation(const osmium::Relation&) noexcept {
+        ++count;
+    }
+
+    void area(const osmium::Area&) noexcept {
+        ++count;
+    }
+
+    void changeset(const osmium::Changeset&) noexcept {
+        ++count;
+    }
+
+    void flush() noexcept {
+        ++count;
+    }
+
+};
+
+struct Handler2 : public osmium::handler::Handler {
+
+    int& count;
+
+    explicit Handler2(int& c) :
+        count(c) {
+    }
+
+    void node(const osmium::Node&) noexcept {
+        count += 2;
+    }
+
+    void way(const osmium::Way&) noexcept {
+        count += 2;
+    }
+
+    void relation(const osmium::Relation&) noexcept {
+        count += 2;
+    }
+
+    void area(const osmium::Area&) noexcept {
+        count += 2;
+    }
+
+    void changeset(const osmium::Changeset&) noexcept {
+        count += 2;
+    }
+
+};
+
+osmium::memory::Buffer fill_buffer() {
+    using namespace osmium::builder::attr;
+    osmium::memory::Buffer buffer{1024 * 1024, osmium::memory::Buffer::auto_grow::yes};
+
+    osmium::builder::add_node(buffer, _id(1));
+    osmium::builder::add_way(buffer, _id(2));
+    osmium::builder::add_relation(buffer, _id(3));
+    osmium::builder::add_area(buffer, _id(4));
+    osmium::builder::add_changeset(buffer, _cid(5));
+
+    return buffer;
+}
+
+TEST_CASE("Base test: static handler") {
+    const auto buffer = fill_buffer();
+
+    int count = 0;
+    Handler1 h1{count};
+    osmium::apply(buffer, h1);
+    REQUIRE(count == 6);
+
+    count = 0;
+    Handler2 h2{count};
+    osmium::apply(buffer, h2);
+    REQUIRE(count == 10);
+}
+
+TEST_CASE("Dynamic handler") {
+    const auto buffer = fill_buffer();
+
+    osmium::handler::DynamicHandler handler;
+    int count = 0;
+
+    osmium::apply(buffer, handler);
+    REQUIRE(count == 0);
+
+    handler.set<Handler1>(count);
+    osmium::apply(buffer, handler);
+    REQUIRE(count == 6);
+
+    count = 0;
+    handler.set<Handler2>(count);
+    osmium::apply(buffer, handler);
+    REQUIRE(count == 10);
+}
+
diff --git a/test/t/index/test_file_based_index.cpp b/test/t/index/test_file_based_index.cpp
index 42cf574..278f43f 100644
--- a/test/t/index/test_file_based_index.cpp
+++ b/test/t/index/test_file_based_index.cpp
@@ -13,7 +13,7 @@
 
 TEST_CASE("File based index") {
 
-    int fd = osmium::detail::create_tmp_file();
+    const int fd = osmium::detail::create_tmp_file();
 
     REQUIRE(osmium::util::file_size(fd) == 0);
 
@@ -27,17 +27,17 @@ TEST_CASE("File based index") {
         constexpr const size_t S = sizeof(index_type::element_type);
 
         {
-            index_type index(fd);
+            index_type index{fd};
 
             REQUIRE(index.size() == 0);
 
-            REQUIRE_THROWS_AS(index.get(  0), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  1), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  3), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  5), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  6), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  7), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+            REQUIRE_THROWS_AS(index.get(  0), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  1), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  3), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  5), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  6), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  7), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
             index.set(id1, loc1);
             REQUIRE(index.size() == 7);
@@ -50,11 +50,11 @@ TEST_CASE("File based index") {
             REQUIRE(loc1 == index.get(id1));
             REQUIRE(loc2 == index.get(id2));
 
-            REQUIRE_THROWS_AS(index.get(  0), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  1), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  5), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  7), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+            REQUIRE_THROWS_AS(index.get(  0), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  1), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  5), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  7), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
             REQUIRE(index.size() == 7);
             REQUIRE(std::distance(index.cbegin(), index.cend()) == 7);
@@ -63,7 +63,7 @@ TEST_CASE("File based index") {
         }
 
         {
-            index_type index(fd);
+            index_type index{fd};
             REQUIRE(osmium::util::file_size(fd) >= (6 * S));
 
             REQUIRE(index.size() == 7);
@@ -71,11 +71,11 @@ TEST_CASE("File based index") {
             REQUIRE(loc1 == index.get(id1));
             REQUIRE(loc2 == index.get(id2));
 
-            REQUIRE_THROWS_AS(index.get(  0), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  1), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  5), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  7), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+            REQUIRE_THROWS_AS(index.get(  0), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  1), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  5), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  7), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
             REQUIRE(index.size() == 7);
             REQUIRE(std::distance(index.cbegin(), index.cend()) == 7);
@@ -97,17 +97,17 @@ TEST_CASE("File based index") {
         constexpr const size_t S = sizeof(index_type::element_type);
 
         {
-            index_type index(fd);
+            index_type index{fd};
 
             REQUIRE(index.size() == 0);
 
-            REQUIRE_THROWS_AS(index.get(  0), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  1), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  3), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  5), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  6), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  7), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+            REQUIRE_THROWS_AS(index.get(  0), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  1), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  3), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  5), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  6), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  7), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
             index.set(id1, loc1);
             REQUIRE(index.size() == 1);
@@ -120,11 +120,11 @@ TEST_CASE("File based index") {
             REQUIRE(loc1 == index.get(id1));
             REQUIRE(loc2 == index.get(id2));
 
-            REQUIRE_THROWS_AS(index.get(  0), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  1), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  5), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  7), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+            REQUIRE_THROWS_AS(index.get(  0), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  1), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  5), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  7), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
             REQUIRE(index.size() == 2);
             REQUIRE(std::distance(index.cbegin(), index.cend()) == 2);
@@ -133,7 +133,7 @@ TEST_CASE("File based index") {
         }
 
         {
-            index_type index(fd);
+            index_type index{fd};
             REQUIRE(osmium::util::file_size(fd) >= (2 * S));
 
             REQUIRE(index.size() == 2);
@@ -141,11 +141,11 @@ TEST_CASE("File based index") {
             REQUIRE(loc1 == index.get(id1));
             REQUIRE(loc2 == index.get(id2));
 
-            REQUIRE_THROWS_AS(index.get(  0), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  1), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  5), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(  7), osmium::not_found);
-            REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+            REQUIRE_THROWS_AS(index.get(  0), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  1), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  5), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(  7), const osmium::not_found&);
+            REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
             REQUIRE(index.size() == 2);
             REQUIRE(std::distance(index.cbegin(), index.cend()) == 2);
diff --git a/test/t/index/test_id_to_location.cpp b/test/t/index/test_id_to_location.cpp
index 9e38d12..4b63314 100644
--- a/test/t/index/test_id_to_location.cpp
+++ b/test/t/index/test_id_to_location.cpp
@@ -7,6 +7,7 @@
 #include <osmium/index/map/dense_mem_array.hpp>
 #include <osmium/index/map/dense_mmap_array.hpp>
 #include <osmium/index/map/dummy.hpp>
+#include <osmium/index/map/flex_mem.hpp>
 #include <osmium/index/map/sparse_file_array.hpp>
 #include <osmium/index/map/sparse_mem_array.hpp>
 #include <osmium/index/map/sparse_mem_map.hpp>
@@ -24,17 +25,17 @@ void test_func_all(TIndex& index) {
     const osmium::Location loc1{1.2, 4.5};
     const osmium::Location loc2{3.5, -7.2};
 
-    REQUIRE_THROWS_AS(index.get(id1), osmium::not_found);
+    REQUIRE_THROWS_AS(index.get(id1), const osmium::not_found&);
 
     index.set(id1, loc1);
     index.set(id2, loc2);
 
     index.sort();
 
-    REQUIRE_THROWS_AS(index.get(0), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(1), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(5), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+    REQUIRE_THROWS_AS(index.get(0), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(1), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(5), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
     REQUIRE_THROWS_WITH(index.get(0), "id 0 not found");
     REQUIRE_THROWS_WITH(index.get(1), "id 1 not found");
 
@@ -62,10 +63,10 @@ void test_func_real(TIndex& index) {
     REQUIRE(loc1 == index.get_noexcept(id1));
     REQUIRE(loc2 == index.get_noexcept(id2));
 
-    REQUIRE_THROWS_AS(index.get(0), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(1), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(5), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+    REQUIRE_THROWS_AS(index.get(0), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(1), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(5), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
     REQUIRE(index.get_noexcept(0) == osmium::Location{});
     REQUIRE(index.get_noexcept(1) == osmium::Location{});
@@ -74,13 +75,13 @@ void test_func_real(TIndex& index) {
 
     index.clear();
 
-    REQUIRE_THROWS_AS(index.get(id1), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(id2), osmium::not_found);
+    REQUIRE_THROWS_AS(index.get(id1), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(id2), const osmium::not_found&);
 
-    REQUIRE_THROWS_AS(index.get(0), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(1), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(5), osmium::not_found);
-    REQUIRE_THROWS_AS(index.get(100), osmium::not_found);
+    REQUIRE_THROWS_AS(index.get(0), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(1), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(5), const osmium::not_found&);
+    REQUIRE_THROWS_AS(index.get(100), const osmium::not_found&);
 
     REQUIRE(index.get_noexcept(id1) == osmium::Location{});
     REQUIRE(index.get_noexcept(id2) == osmium::Location{});
@@ -180,15 +181,67 @@ TEST_CASE("Map Id to location: SparseMemArray") {
     test_func_real<index_type>(index2);
 }
 
+TEST_CASE("Map Id to location: FlexMem sparse") {
+    using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
+
+    index_type index1;
+    test_func_all<index_type>(index1);
+
+    index_type index2;
+    test_func_real<index_type>(index2);
+}
+
+TEST_CASE("Map Id to location: FlexMem dense") {
+    using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
+
+    index_type index1{true};
+    test_func_all<index_type>(index1);
+
+    index_type index2{true};
+    test_func_real<index_type>(index2);
+}
+
+TEST_CASE("Map Id to location: FlexMem switch") {
+    using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
+
+    const osmium::Location loc1{1.1, 1.2};
+    const osmium::Location loc2{2.2, -9.4};
+
+    index_type index;
+
+    REQUIRE(index.size() == 0);
+
+    index.set(17, loc1);
+    index.set(99, loc2);
+
+    REQUIRE_FALSE(index.is_dense());
+    REQUIRE(index.size() == 2);
+    REQUIRE(index.get_noexcept(0) == osmium::Location{});
+    REQUIRE(index.get_noexcept(1) == osmium::Location{});
+    REQUIRE(index.get_noexcept(17) == loc1);
+    REQUIRE(index.get_noexcept(99) == loc2);
+    REQUIRE(index.get_noexcept(2000000000) == osmium::Location{});
+
+    index.switch_to_dense();
+
+    REQUIRE(index.is_dense());
+    REQUIRE(index.size() >= 2);
+    REQUIRE(index.get_noexcept(0) == osmium::Location{});
+    REQUIRE(index.get_noexcept(1) == osmium::Location{});
+    REQUIRE(index.get_noexcept(17) == loc1);
+    REQUIRE(index.get_noexcept(99) == loc2);
+    REQUIRE(index.get_noexcept(2000000000) == osmium::Location{});
+}
+
 TEST_CASE("Map Id to location: Dynamic map choice") {
     using map_type = osmium::index::map::Map<osmium::unsigned_object_id_type, osmium::Location>;
     const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
 
     const std::vector<std::string> map_type_names = map_factory.map_types();
-    REQUIRE(map_type_names.size() >= 5);
+    REQUIRE(map_type_names.size() >= 6);
 
-    REQUIRE_THROWS_AS(map_factory.create_map(""), osmium::map_factory_error);
-    REQUIRE_THROWS_AS(map_factory.create_map("does not exist"), osmium::map_factory_error);
+    REQUIRE_THROWS_AS(map_factory.create_map(""), const osmium::map_factory_error&);
+    REQUIRE_THROWS_AS(map_factory.create_map("does not exist"), const osmium::map_factory_error&);
     REQUIRE_THROWS_WITH(map_factory.create_map(""), "Need non-empty map type name");
     REQUIRE_THROWS_WITH(map_factory.create_map("does not exist"), "Support for map type 'does not exist' not compiled into this binary");
 
diff --git a/test/t/io/test_bzip2.cpp b/test/t/io/test_bzip2.cpp
index 2d1c0f3..3e3fe81 100644
--- a/test/t/io/test_bzip2.cpp
+++ b/test/t/io/test_bzip2.cpp
@@ -8,7 +8,7 @@
 #include <osmium/io/bzip2_compression.hpp>
 
 TEST_CASE("Read bzip2-compressed file") {
-    std::string input_file = with_data_dir("t/io/data_bzip2.txt.bz2");
+    const std::string input_file = with_data_dir("t/io/data_bzip2.txt.bz2");
 
     const int fd = ::open(input_file.c_str(), O_RDONLY);
     REQUIRE(fd > 0);
diff --git a/test/t/io/test_compression_factory.cpp b/test/t/io/test_compression_factory.cpp
index 612d232..97cb153 100644
--- a/test/t/io/test_compression_factory.cpp
+++ b/test/t/io/test_compression_factory.cpp
@@ -3,22 +3,21 @@
 
 #include <osmium/io/compression.hpp>
 
-TEST_CASE("Compression factory") {
+TEST_CASE("Create compressor using factory") {
     const auto& factory = osmium::io::CompressionFactory::instance();
+    REQUIRE(factory.create_compressor(osmium::io::file_compression::none, -1, osmium::io::fsync::no));
+}
 
-    SECTION("compressor") {
-        REQUIRE(factory.create_compressor(osmium::io::file_compression::none, -1, osmium::io::fsync::no));
-    }
-
-    SECTION("decompressor") {
-        REQUIRE(factory.create_decompressor(osmium::io::file_compression::none, nullptr, 0));
-    }
+TEST_CASE("Create decompressor using factory") {
+    const auto& factory = osmium::io::CompressionFactory::instance();
+    REQUIRE(factory.create_decompressor(osmium::io::file_compression::none, nullptr, 0));
+}
 
-    SECTION("fail on undefined compression") {
-        REQUIRE_THROWS_AS(factory.create_compressor(osmium::io::file_compression::gzip, -1, osmium::io::fsync::no),
-                          osmium::unsupported_file_format_error);
-        REQUIRE_THROWS_WITH(factory.create_compressor(osmium::io::file_compression::gzip, -1, osmium::io::fsync::no),
-                            "Support for compression 'gzip' not compiled into this binary");
-    }
+TEST_CASE("Compression factory fails on undefined compression") {
+    const auto& factory = osmium::io::CompressionFactory::instance();
+    REQUIRE_THROWS_AS(factory.create_compressor(osmium::io::file_compression::gzip, -1, osmium::io::fsync::no),
+                      const osmium::unsupported_file_format_error&);
+    REQUIRE_THROWS_WITH(factory.create_compressor(osmium::io::file_compression::gzip, -1, osmium::io::fsync::no),
+                        "Support for compression 'gzip' not compiled into this binary");
 }
 
diff --git a/test/t/io/test_file_formats.cpp b/test/t/io/test_file_formats.cpp
index 356e3d1..8b63f94 100644
--- a/test/t/io/test_file_formats.cpp
+++ b/test/t/io/test_file_formats.cpp
@@ -5,31 +5,31 @@
 #include <osmium/io/file.hpp>
 
 TEST_CASE("Default file format") {
-    osmium::io::File f;
+    const osmium::io::File f;
     REQUIRE(osmium::io::file_format::unknown == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("File format when empty (stdin/stdout)") {
-    osmium::io::File f{""};
+    const osmium::io::File f{""};
     REQUIRE(osmium::io::file_format::unknown == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("File format from dash (stdin/stdout)") {
-    osmium::io::File f{"-"};
+    const osmium::io::File f{"-"};
     REQUIRE(osmium::io::file_format::unknown == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("File format from dash with osm.bz2") {
-    osmium::io::File f{"-", "osm.bz2"};
+    const osmium::io::File f{"-", "osm.bz2"};
     REQUIRE("" == f.filename());
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::bzip2 == f.compression());
@@ -38,7 +38,7 @@ TEST_CASE("File format from dash with osm.bz2") {
 }
 
 TEST_CASE("Detect file format by suffix 'osm'") {
-    osmium::io::File f{"test.osm"};
+    const osmium::io::File f{"test.osm"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -46,7 +46,7 @@ TEST_CASE("Detect file format by suffix 'osm'") {
 }
 
 TEST_CASE("Detect file format by suffix 'pbf'") {
-    osmium::io::File f{"test.pbf"};
+    const osmium::io::File f{"test.pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -54,7 +54,7 @@ TEST_CASE("Detect file format by suffix 'pbf'") {
 }
 
 TEST_CASE("Detect file format by suffix 'osm.pbf'") {
-    osmium::io::File f{"test.osm.pbf"};
+    const osmium::io::File f{"test.osm.pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -62,7 +62,7 @@ TEST_CASE("Detect file format by suffix 'osm.pbf'") {
 }
 
 TEST_CASE("Detect file format by suffix 'opl'") {
-    osmium::io::File f{"test.opl"};
+    const osmium::io::File f{"test.opl"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -70,7 +70,7 @@ TEST_CASE("Detect file format by suffix 'opl'") {
 }
 
 TEST_CASE("Detect file format by suffix 'osm.opl'") {
-    osmium::io::File f{"test.osm.opl"};
+    const osmium::io::File f{"test.osm.opl"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -78,7 +78,7 @@ TEST_CASE("Detect file format by suffix 'osm.opl'") {
 }
 
 TEST_CASE("Detect file format by suffix 'osm.gz'") {
-    osmium::io::File f{"test.osm.gz"};
+    const osmium::io::File f{"test.osm.gz"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::gzip == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -86,7 +86,7 @@ TEST_CASE("Detect file format by suffix 'osm.gz'") {
 }
 
 TEST_CASE("Detect file format by suffix 'opl.bz2'") {
-    osmium::io::File f{"test.osm.opl.bz2"};
+    const osmium::io::File f{"test.osm.opl.bz2"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::bzip2 == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -94,7 +94,7 @@ TEST_CASE("Detect file format by suffix 'opl.bz2'") {
 }
 
 TEST_CASE("Detect file format by suffix 'osc.gz'") {
-    osmium::io::File f{"test.osc.gz"};
+    const osmium::io::File f{"test.osc.gz"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::gzip == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -102,7 +102,7 @@ TEST_CASE("Detect file format by suffix 'osc.gz'") {
 }
 
 TEST_CASE("Detect file format by suffix 'opl.gz'") {
-    osmium::io::File f{"test.osh.opl.gz"};
+    const osmium::io::File f{"test.osh.opl.gz"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::gzip == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -110,7 +110,7 @@ TEST_CASE("Detect file format by suffix 'opl.gz'") {
 }
 
 TEST_CASE("Detect file format by suffix 'osh.pbf'") {
-    osmium::io::File f{"test.osh.pbf"};
+    const osmium::io::File f{"test.osh.pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -118,7 +118,7 @@ TEST_CASE("Detect file format by suffix 'osh.pbf'") {
 }
 
 TEST_CASE("Override file format by suffix 'osm'") {
-    osmium::io::File f{"test", "osm"};
+    const osmium::io::File f{"test", "osm"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -126,7 +126,7 @@ TEST_CASE("Override file format by suffix 'osm'") {
 }
 
 TEST_CASE("Override file format by suffix 'pbf'") {
-    osmium::io::File f{"test", "pbf"};
+    const osmium::io::File f{"test", "pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -134,7 +134,7 @@ TEST_CASE("Override file format by suffix 'pbf'") {
 }
 
 TEST_CASE("Override file format by suffix 'osm.pbf'") {
-    osmium::io::File f{"test", "osm.pbf"};
+    const osmium::io::File f{"test", "osm.pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -142,7 +142,7 @@ TEST_CASE("Override file format by suffix 'osm.pbf'") {
 }
 
 TEST_CASE("Override file format by suffix 'opl'") {
-    osmium::io::File f{"test", "opl"};
+    const osmium::io::File f{"test", "opl"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -150,7 +150,7 @@ TEST_CASE("Override file format by suffix 'opl'") {
 }
 
 TEST_CASE("Override file format by suffix 'osm.opl'") {
-    osmium::io::File f{"test", "osm.opl"};
+    const osmium::io::File f{"test", "osm.opl"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -158,7 +158,7 @@ TEST_CASE("Override file format by suffix 'osm.opl'") {
 }
 
 TEST_CASE("Override file format by suffix 'osm.gz'") {
-    osmium::io::File f{"test", "osm.gz"};
+    const osmium::io::File f{"test", "osm.gz"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::gzip == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -166,7 +166,7 @@ TEST_CASE("Override file format by suffix 'osm.gz'") {
 }
 
 TEST_CASE("Override file format by suffix 'osm.opl.bz2'") {
-    osmium::io::File f{"test", "osm.opl.bz2"};
+    const osmium::io::File f{"test", "osm.opl.bz2"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::bzip2 == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -174,7 +174,7 @@ TEST_CASE("Override file format by suffix 'osm.opl.bz2'") {
 }
 
 TEST_CASE("Override file format by suffix 'osc.gz'") {
-    osmium::io::File f{"test", "osc.gz"};
+    const osmium::io::File f{"test", "osc.gz"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::gzip == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -182,7 +182,7 @@ TEST_CASE("Override file format by suffix 'osc.gz'") {
 }
 
 TEST_CASE("Override file format by suffix 'osh.opl.gz'") {
-    osmium::io::File f{"test", "osh.opl.gz"};
+    const osmium::io::File f{"test", "osh.opl.gz"};
     REQUIRE(osmium::io::file_format::opl == f.format());
     REQUIRE(osmium::io::file_compression::gzip == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -190,7 +190,7 @@ TEST_CASE("Override file format by suffix 'osh.opl.gz'") {
 }
 
 TEST_CASE("Override file format by suffix 'osh.pbf'") {
-    osmium::io::File f{"test", "osh.pbf"};
+    const osmium::io::File f{"test", "osh.pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -198,7 +198,7 @@ TEST_CASE("Override file format by suffix 'osh.pbf'") {
 }
 
 TEST_CASE("Format option pbf history") {
-    osmium::io::File f{"test", "pbf,history=true"};
+    const osmium::io::File f{"test", "pbf,history=true"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
@@ -206,7 +206,7 @@ TEST_CASE("Format option pbf history") {
 }
 
 TEST_CASE("Format option pbf foo") {
-    osmium::io::File f{"test.osm", "pbf,foo=bar"};
+    const osmium::io::File f{"test.osm", "pbf,foo=bar"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE("bar" == f.get("foo"));
@@ -214,7 +214,7 @@ TEST_CASE("Format option pbf foo") {
 }
 
 TEST_CASE("Format option xml abc something") {
-    osmium::io::File f{"test.bla", "xml,abc,some=thing"};
+    const osmium::io::File f{"test.bla", "xml,abc,some=thing"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE("true" == f.get("abc"));
@@ -224,29 +224,29 @@ TEST_CASE("Format option xml abc something") {
 }
 
 TEST_CASE("Unknown format 'foo.bar'") {
-    osmium::io::File f{"test.foo.bar"};
+    const osmium::io::File f{"test.foo.bar"};
     REQUIRE(osmium::io::file_format::unknown == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("Unknown format 'foo'") {
-    osmium::io::File f{"test", "foo"};
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    const osmium::io::File f{"test", "foo"};
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("Unknown format 'osm.foo'") {
-    osmium::io::File f{"test", "osm.foo"};
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    const osmium::io::File f{"test", "osm.foo"};
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("Unknown format 'bla=foo'") {
-    osmium::io::File f{"test", "bla=foo"};
-    REQUIRE_THROWS_AS(f.check(), std::runtime_error);
+    const osmium::io::File f{"test", "bla=foo"};
+    REQUIRE_THROWS_AS(f.check(), const std::runtime_error&);
 }
 
 TEST_CASE("URL without format") {
-    osmium::io::File f{"http://www.example.com/api"};
+    const osmium::io::File f{"http://www.example.com/api"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -254,7 +254,7 @@ TEST_CASE("URL without format") {
 }
 
 TEST_CASE("URL without format and filename") {
-    osmium::io::File f{"http://planet.osm.org/pbf/planet-latest.osm.pbf"};
+    const osmium::io::File f{"http://planet.osm.org/pbf/planet-latest.osm.pbf"};
     REQUIRE(osmium::io::file_format::pbf == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(false == f.has_multiple_object_versions());
@@ -262,7 +262,7 @@ TEST_CASE("URL without format and filename") {
 }
 
 TEST_CASE("URL with format") {
-    osmium::io::File f{"http://www.example.com/api", "osh"};
+    const osmium::io::File f{"http://www.example.com/api", "osh"};
     REQUIRE(osmium::io::file_format::xml == f.format());
     REQUIRE(osmium::io::file_compression::none == f.compression());
     REQUIRE(true == f.has_multiple_object_versions());
diff --git a/test/t/io/test_opl_parser.cpp b/test/t/io/test_opl_parser.cpp
index 8e2cdb7..7ecf2b8 100644
--- a/test/t/io/test_opl_parser.cpp
+++ b/test/t/io/test_opl_parser.cpp
@@ -12,7 +12,7 @@
 namespace oid = osmium::io::detail;
 
 TEST_CASE("Parse OPL: base exception") {
-    osmium::opl_error e{"foo"};
+    const osmium::opl_error e{"foo"};
     REQUIRE(e.data == nullptr);
     REQUIRE(e.line == 0);
     REQUIRE(e.column == 0);
@@ -32,16 +32,16 @@ TEST_CASE("Parse OPL: exception with line and column") {
 }
 
 TEST_CASE("Parse OPL: space") {
-    std::string d{"a b \t c"};
+    const std::string d{"a b \t c"};
 
     const char* s = d.data();
-    REQUIRE_THROWS_AS(oid::opl_parse_space(&s), osmium::opl_error);
+    REQUIRE_THROWS_AS(oid::opl_parse_space(&s), const osmium::opl_error&);
 
     s = d.data() + 1;
     oid::opl_parse_space(&s);
     REQUIRE(*s == 'b');
 
-    REQUIRE_THROWS_AS(oid::opl_parse_space(&s), osmium::opl_error);
+    REQUIRE_THROWS_AS(oid::opl_parse_space(&s), const osmium::opl_error&);
 
     ++s;
     oid::opl_parse_space(&s);
@@ -58,7 +58,7 @@ TEST_CASE("Parse OPL: check for space") {
 }
 
 TEST_CASE("Parse OPL: skip section") {
-    std::string d{"abcd efgh"};
+    const std::string d{"abcd efgh"};
     const char* skip1 = d.data() + 4;
     const char* skip2 = d.data() + 9;
     const char* s = d.data();
@@ -245,7 +245,7 @@ TEST_CASE("Parse OPL: parse string") {
 
 template <typename T = int64_t>
 T test_parse_int(const char* s) {
-    auto r = oid::opl_parse_int<T>(&s);
+    const auto r = oid::opl_parse_int<T>(&s);
     REQUIRE(*s == 'x');
     return r;
 }
@@ -916,28 +916,28 @@ TEST_CASE("Parse line") {
     SECTION("Node") {
         REQUIRE(oid::opl_parse_line(0, "n12 v3", buffer));
         REQUIRE(buffer.written() > 0);
-        auto& item = buffer.get<osmium::memory::Item>(0);
+        const auto& item = buffer.get<osmium::memory::Item>(0);
         REQUIRE(item.type() == osmium::item_type::node);
     }
 
     SECTION("Way") {
         REQUIRE(oid::opl_parse_line(0, "w12 v3", buffer));
         REQUIRE(buffer.written() > 0);
-        auto& item = buffer.get<osmium::memory::Item>(0);
+        const auto& item = buffer.get<osmium::memory::Item>(0);
         REQUIRE(item.type() == osmium::item_type::way);
     }
 
     SECTION("Relation") {
         REQUIRE(oid::opl_parse_line(0, "r12 v3", buffer));
         REQUIRE(buffer.written() > 0);
-        auto& item = buffer.get<osmium::memory::Item>(0);
+        const auto& item = buffer.get<osmium::memory::Item>(0);
         REQUIRE(item.type() == osmium::item_type::relation);
     }
 
     SECTION("Changeset") {
         REQUIRE(oid::opl_parse_line(0, "c12", buffer));
         REQUIRE(buffer.written() > 0);
-        auto& item = buffer.get<osmium::memory::Item>(0);
+        const auto& item = buffer.get<osmium::memory::Item>(0);
         REQUIRE(item.type() == osmium::item_type::changeset);
     }
 
diff --git a/test/t/io/test_output_iterator.cpp b/test/t/io/test_output_iterator.cpp
index f554da2..df032a8 100644
--- a/test/t/io/test_output_iterator.cpp
+++ b/test/t/io/test_output_iterator.cpp
@@ -4,33 +4,30 @@
 #include <osmium/io/output_iterator.hpp>
 #include <osmium/io/writer.hpp>
 
-TEST_CASE("output iterator") {
+TEST_CASE("Output iterator should be copy constructable") {
+    const osmium::io::Header header;
+    osmium::io::Writer writer{"test.osm", header, osmium::io::overwrite::allow};
 
-    osmium::io::Header header;
-
-    SECTION("should be copy constructable") {
-        osmium::io::Writer writer{"test.osm", header, osmium::io::overwrite::allow};
-        osmium::io::OutputIterator<osmium::io::Writer> out1{writer};
-
-        osmium::io::OutputIterator<osmium::io::Writer> out2{out1};
-    }
-
-    SECTION("should be copy assignable") {
-        osmium::io::Writer writer1{"test1.osm", header, osmium::io::overwrite::allow};
-        osmium::io::Writer writer2{"test2.osm", header, osmium::io::overwrite::allow};
+    osmium::io::OutputIterator<osmium::io::Writer> out1{writer};
+    osmium::io::OutputIterator<osmium::io::Writer> out2{out1};
+}
 
-        osmium::io::OutputIterator<osmium::io::Writer> out1{writer1};
-        osmium::io::OutputIterator<osmium::io::Writer> out2{writer2};
+TEST_CASE("Output iterator should be copy assignable") {
+    const osmium::io::Header header;
+    osmium::io::Writer writer1{"test1.osm", header, osmium::io::overwrite::allow};
+    osmium::io::Writer writer2{"test2.osm", header, osmium::io::overwrite::allow};
 
-        out2 = out1;
-    }
+    osmium::io::OutputIterator<osmium::io::Writer> out1{writer1};
+    osmium::io::OutputIterator<osmium::io::Writer> out2{writer2};
 
-    SECTION("should be incrementable") {
-        osmium::io::Writer writer{"test.osm", header, osmium::io::overwrite::allow};
-        osmium::io::OutputIterator<osmium::io::Writer> out{writer};
+    out2 = out1;
+}
 
-        ++out;
-    }
+TEST_CASE("Output iterator should be incrementable") {
+    const osmium::io::Header header;
+    osmium::io::Writer writer{"test.osm", header, osmium::io::overwrite::allow};
+    osmium::io::OutputIterator<osmium::io::Writer> out{writer};
 
+    ++out;
 }
 
diff --git a/test/t/io/test_reader.cpp b/test/t/io/test_reader.cpp
index 45c087d..10cc3b1 100644
--- a/test/t/io/test_reader.cpp
+++ b/test/t/io/test_reader.cpp
@@ -54,6 +54,15 @@ TEST_CASE("Reader can be initialized with string") {
     osmium::apply(reader, handler);
 }
 
+TEST_CASE("Reader can be initialized with user-provided pool") {
+    osmium::thread::Pool pool{4};
+    osmium::io::File file{with_data_dir("t/io/data.osm")};
+    osmium::io::Reader reader{file, pool};
+    osmium::handler::Handler handler;
+
+    osmium::apply(reader, handler);
+}
+
 TEST_CASE("Reader should throw after eof") {
     osmium::io::File file{with_data_dir("t/io/data.osm")};
     osmium::io::Reader reader{file};
@@ -71,7 +80,7 @@ TEST_CASE("Reader should throw after eof") {
 
     REQUIRE(reader.eof());
 
-    REQUIRE_THROWS_AS(reader.read(), osmium::io_error);
+    REQUIRE_THROWS_AS(reader.read(), const osmium::io_error&);
 
     reader.close();
     REQUIRE(reader.eof());
@@ -83,7 +92,7 @@ TEST_CASE("Reader should not hang when apply() is called twice on reader") {
     osmium::handler::Handler handler;
 
     osmium::apply(reader, handler);
-    REQUIRE_THROWS_AS(osmium::apply(reader, handler), osmium::io_error);
+    REQUIRE_THROWS_AS(osmium::apply(reader, handler), const osmium::io_error&);
 }
 
 TEST_CASE("Reader should work with a buffer with uncompressed data") {
@@ -183,7 +192,7 @@ TEST_CASE("Reader should fail with nonexistent file (pbf)") {
 TEST_CASE("Reader should work when there is an exception in main thread before getting header") {
     try {
         osmium::io::Reader reader{with_data_dir("t/io/data.osm")};
-        REQUIRE(!reader.eof());
+        REQUIRE_FALSE(reader.eof());
         throw std::runtime_error{"foo"};
     } catch (...) {
     }
@@ -192,7 +201,7 @@ TEST_CASE("Reader should work when there is an exception in main thread before g
 TEST_CASE("Reader should work when there is an exception in main thread while reading") {
     try {
         osmium::io::Reader reader{with_data_dir("t/io/data.osm")};
-        REQUIRE(!reader.eof());
+        REQUIRE_FALSE(reader.eof());
         auto header = reader.header();
         throw std::runtime_error{"foo"};
     } catch (...) {
@@ -219,7 +228,7 @@ TEST_CASE("Can call read() exactly once on Reader with entity_bits nothing") {
     osmium::memory::Buffer buffer = reader.read();
     REQUIRE_FALSE(buffer);
     REQUIRE(reader.eof());
-    REQUIRE_THROWS_AS(reader.read(), osmium::io_error);
+    REQUIRE_THROWS_AS(reader.read(), const osmium::io_error&);
 
     reader.close();
     REQUIRE(reader.eof());
@@ -243,6 +252,6 @@ TEST_CASE("Can not read after close") {
 
     reader.close();
     REQUIRE(reader.eof());
-    REQUIRE_THROWS_AS(reader.read(), osmium::io_error);
+    REQUIRE_THROWS_AS(reader.read(), const osmium::io_error&);
 }
 
diff --git a/test/t/io/test_reader_fileformat.cpp b/test/t/io/test_reader_fileformat.cpp
index a64932e..83b4a0d 100644
--- a/test/t/io/test_reader_fileformat.cpp
+++ b/test/t/io/test_reader_fileformat.cpp
@@ -5,6 +5,6 @@
 #include <osmium/io/reader.hpp>
 
 TEST_CASE("Reader throws on unsupported file format") {
-    REQUIRE_THROWS_AS(osmium::io::Reader{with_data_dir("t/io/data.osm")}, osmium::unsupported_file_format_error);
+    REQUIRE_THROWS_AS(osmium::io::Reader{with_data_dir("t/io/data.osm")}, const osmium::unsupported_file_format_error&);
 }
 
diff --git a/test/t/io/test_reader_with_mock_parser.cpp b/test/t/io/test_reader_with_mock_parser.cpp
index 4076964..f0aef07 100644
--- a/test/t/io/test_reader_with_mock_parser.cpp
+++ b/test/t/io/test_reader_with_mock_parser.cpp
@@ -62,7 +62,7 @@ TEST_CASE("Test Reader using MockParser") {
         osmium::io::Reader reader{with_data_dir("t/io/data.osm")};
         auto header = reader.header();
         REQUIRE(reader.read());
-        REQUIRE(!reader.read());
+        REQUIRE_FALSE(reader.read());
         REQUIRE(reader.eof());
         reader.close();
     }
@@ -99,7 +99,7 @@ TEST_CASE("Test Reader using MockParser") {
             REQUIRE(std::string{e.what()} == "error in user code");
         }
         REQUIRE(reader.read());
-        REQUIRE(!reader.read());
+        REQUIRE_FALSE(reader.read());
         REQUIRE(reader.eof());
         reader.close();
     }
diff --git a/test/t/io/test_string_table.cpp b/test/t/io/test_string_table.cpp
index 38418a0..6b7ccba 100644
--- a/test/t/io/test_string_table.cpp
+++ b/test/t/io/test_string_table.cpp
@@ -2,119 +2,125 @@
 
 #include <osmium/io/detail/string_table.hpp>
 
-TEST_CASE("String store") {
+TEST_CASE("Empty StringStore") {
+    const osmium::io::detail::StringStore ss{100};
+
+    REQUIRE(ss.begin() == ss.end());
+    REQUIRE(ss.get_chunk_size() == 100);
+    REQUIRE(ss.get_chunk_count() == 1);
+}
+
+TEST_CASE("Add zero-length string to StringStore") {
     osmium::io::detail::StringStore ss{100};
 
-    SECTION("empty") {
-        REQUIRE(ss.begin() == ss.end());
-        REQUIRE(ss.get_chunk_size() == 100);
-        REQUIRE(ss.get_chunk_count() == 1);
-    }
+    const char* s1 = ss.add("");
+    REQUIRE(std::string(s1) == "");
 
-    SECTION("add zero-length string") {
-        const char* s1 = ss.add("");
-        REQUIRE(std::string(s1) == "");
+    auto it = ss.begin();
+    REQUIRE(s1 == *it);
+    REQUIRE(std::string(*it) == "");
+    REQUIRE(++it == ss.end());
 
-        auto it = ss.begin();
-        REQUIRE(s1 == *it);
-        REQUIRE(std::string(*it) == "");
-        REQUIRE(++it == ss.end());
+    REQUIRE(ss.get_chunk_count() == 1);
+}
 
-        REQUIRE(ss.get_chunk_count() == 1);
-    }
+TEST_CASE("Add strings to StringStore") {
+    osmium::io::detail::StringStore ss{100};
 
-    SECTION("add strings") {
-        const char* s1 = ss.add("foo");
-        const char* s2 = ss.add("bar");
-        REQUIRE(s1 != s2);
-        REQUIRE(std::string(s1) == "foo");
-        REQUIRE(std::string(s2) == "bar");
+    const char* s1 = ss.add("foo");
+    const char* s2 = ss.add("bar");
+    REQUIRE(s1 != s2);
+    REQUIRE(std::string(s1) == "foo");
+    REQUIRE(std::string(s2) == "bar");
 
-        auto it = ss.begin();
-        REQUIRE(s1 == *it++);
-        REQUIRE(s2 == *it++);
-        REQUIRE(it == ss.end());
+    auto it = ss.begin();
+    REQUIRE(s1 == *it++);
+    REQUIRE(s2 == *it++);
+    REQUIRE(it == ss.end());
 
-        ss.clear();
-        REQUIRE(ss.begin() == ss.end());
-    }
+    ss.clear();
+    REQUIRE(ss.begin() == ss.end());
+}
 
-    SECTION("add zero-length string and longer strings") {
-        ss.add("");
-        ss.add("xxx");
-        ss.add("yyyyy");
+TEST_CASE("Add zero-length string and longer strings to StringStore") {
+    osmium::io::detail::StringStore ss{100};
 
-        auto it = ss.begin();
-        REQUIRE(std::string(*it++) == "");
-        REQUIRE(std::string(*it++) == "xxx");
-        REQUIRE(std::string(*it++) == "yyyyy");
-        REQUIRE(it == ss.end());
-    }
+    ss.add("");
+    ss.add("xxx");
+    ss.add("yyyyy");
+
+    auto it = ss.begin();
+    REQUIRE(std::string(*it++) == "");
+    REQUIRE(std::string(*it++) == "xxx");
+    REQUIRE(std::string(*it++) == "yyyyy");
+    REQUIRE(it == ss.end());
+}
+
+TEST_CASE("Add many strings to StringStore") {
+    osmium::io::detail::StringStore ss{100};
 
-    SECTION("add many strings") {
-        for (const char* teststring : {"", "a", "abc", "abcd", "abcde"}) {
-            int i = 0;
-            for (; i < 200; ++i) {
-                ss.add(teststring);
-            }
-
-            for (const char* s : ss) {
-                REQUIRE(std::string(s) == teststring);
-                --i;
-            }
-
-            REQUIRE(i == 0);
-            REQUIRE(ss.get_chunk_count() > 1);
-            ss.clear();
-            REQUIRE(ss.get_chunk_count() == 1);
+    for (const char* teststring : {"", "a", "abc", "abcd", "abcde"}) {
+        int i = 0;
+        for (; i < 200; ++i) {
+            ss.add(teststring);
         }
+
+        for (const char* s : ss) {
+            REQUIRE(std::string(s) == teststring);
+            --i;
+        }
+
+        REQUIRE(i == 0);
+        REQUIRE(ss.get_chunk_count() > 1);
+        ss.clear();
+        REQUIRE(ss.get_chunk_count() == 1);
     }
+}
+
+TEST_CASE("Empty StringTable") {
+    const osmium::io::detail::StringTable st;
 
+    REQUIRE(st.size() == 1);
+    REQUIRE(std::next(st.begin()) == st.end());
 }
 
-TEST_CASE("String table") {
+TEST_CASE("Add strings to StringTable") {
     osmium::io::detail::StringTable st;
 
-    SECTION("empty") {
-        REQUIRE(st.size() == 1);
-        REQUIRE(std::next(st.begin()) == st.end());
-    }
+    REQUIRE(st.add("foo") == 1);
+    REQUIRE(st.add("bar") == 2);
+    REQUIRE(st.add("bar") == 2);
+    REQUIRE(st.add("baz") == 3);
+    REQUIRE(st.add("foo") == 1);
+    REQUIRE(st.size() == 4);
 
-    SECTION("add strings") {
-        REQUIRE(st.add("foo") == 1);
-        REQUIRE(st.add("bar") == 2);
-        REQUIRE(st.add("bar") == 2);
-        REQUIRE(st.add("baz") == 3);
-        REQUIRE(st.add("foo") == 1);
-        REQUIRE(st.size() == 4);
-
-        auto it = st.begin();
-        REQUIRE(std::string("") == *it++);
-        REQUIRE(std::string("foo") == *it++);
-        REQUIRE(std::string("bar") == *it++);
-        REQUIRE(std::string("baz") == *it++);
-        REQUIRE(it == st.end());
-
-        st.clear();
-        REQUIRE(st.size() == 1);
-    }
+    auto it = st.begin();
+    REQUIRE(std::string("") == *it++);
+    REQUIRE(std::string("foo") == *it++);
+    REQUIRE(std::string("bar") == *it++);
+    REQUIRE(std::string("baz") == *it++);
+    REQUIRE(it == st.end());
 
-    SECTION("add empty string") {
-        REQUIRE(st.add("") == 1);
-        REQUIRE(st.size() == 2);
-        REQUIRE(st.add("") == 1);
-        REQUIRE(st.size() == 2);
-    }
+    st.clear();
+    REQUIRE(st.size() == 1);
+}
+
+TEST_CASE("Add empty string to StringTable") {
+    osmium::io::detail::StringTable st;
 
+    REQUIRE(st.add("") == 1);
+    REQUIRE(st.size() == 2);
+    REQUIRE(st.add("") == 1);
+    REQUIRE(st.size() == 2);
 }
 
-TEST_CASE("lots of strings in string table so chunk overflows") {
+TEST_CASE("Lots of strings in string table so chunk overflows") {
     osmium::io::detail::StringTable st{100};
     REQUIRE(st.size() == 1);
 
     const int n = 1000;
     for (int i = 0; i < n; ++i) {
-        auto s = std::to_string(i);
+        const auto s = std::to_string(i);
         st.add(s.c_str());
     }
 
@@ -123,7 +129,7 @@ TEST_CASE("lots of strings in string table so chunk overflows") {
     auto it = st.begin();
     REQUIRE(std::string{} == *it++);
     for (int i = 0; i < n; ++i) {
-        REQUIRE(atoi(*it++) == i);
+        REQUIRE(std::atoi(*it++) == i);
     }
     REQUIRE(it == st.end());
 }
diff --git a/test/t/io/test_writer.cpp b/test/t/io/test_writer.cpp
index 38ec6ad..9f47b77 100644
--- a/test/t/io/test_writer.cpp
+++ b/test/t/io/test_writer.cpp
@@ -43,10 +43,10 @@ TEST_CASE("Writer") {
 
         osmium::io::Reader reader_check{filename};
         osmium::memory::Buffer buffer_check = reader_check.read();
-        REQUIRE(!buffer_check);
+        REQUIRE_FALSE(buffer_check);
     }
 
-    SECTION("Successfull writes") {
+    SECTION("Successful writes") {
 
         SECTION("Writer buffer") {
             filename = "test-writer-out-buffer.osm";
@@ -54,7 +54,7 @@ TEST_CASE("Writer") {
             writer(std::move(buffer));
             writer.close();
 
-            REQUIRE_THROWS_AS(writer(osmium::memory::Buffer{}), osmium::io_error);
+            REQUIRE_THROWS_AS(writer(osmium::memory::Buffer{}), const osmium::io_error&);
         }
 
         SECTION("Writer item") {
@@ -75,7 +75,7 @@ TEST_CASE("Writer") {
         }
 
         osmium::io::Reader reader_check{filename};
-        osmium::memory::Buffer buffer_check = reader_check.read();
+        const osmium::memory::Buffer buffer_check = reader_check.read();
         REQUIRE(buffer_check);
         REQUIRE(buffer_check.committed() > 0);
         REQUIRE(buffer_check.select<osmium::OSMObject>().size() == num);
@@ -111,3 +111,40 @@ TEST_CASE("Writer") {
 
 }
 
+TEST_CASE("Writer with user-provided pool") {
+    osmium::io::Header header;
+    header.set("generator", "test_writer.cpp");
+
+    osmium::io::Reader reader{with_data_dir("t/io/data.osm")};
+    osmium::memory::Buffer buffer = reader.read();
+    REQUIRE(buffer);
+    REQUIRE(buffer.committed() > 0);
+
+    SECTION("with default number of threads") {
+        osmium::thread::Pool pool;
+        osmium::io::Writer writer{"test-writer-pool-with-default-threads.osm", pool, header, osmium::io::overwrite::allow};
+        writer(std::move(buffer));
+        writer.close();
+    }
+
+    SECTION("with negative number of threads") {
+        osmium::thread::Pool pool{-2};
+        osmium::io::Writer writer{"test-writer-pool-with-negative-threads.osm", header, pool, osmium::io::overwrite::allow};
+        writer(std::move(buffer));
+        writer.close();
+    }
+
+    SECTION("with outlier negative number of threads") {
+        osmium::thread::Pool pool{-1000};
+        osmium::io::Writer writer{"test-writer-pool-with-outlier-negative-threads.osm", header, osmium::io::overwrite::allow, pool};
+        writer(std::move(buffer));
+        writer.close();
+    }
+
+    SECTION("with outlier positive number of threads") {
+        osmium::thread::Pool pool{1000};
+        osmium::io::Writer writer{"test-writer-pool-with-outlier-positive-threads.osm", header, osmium::io::overwrite::allow, pool};
+        writer(std::move(buffer));
+        writer.close();
+    }
+}
diff --git a/test/t/io/test_writer_with_mock_compression.cpp b/test/t/io/test_writer_with_mock_compression.cpp
index a4f9de2..c297462 100644
--- a/test/t/io/test_writer_with_mock_compression.cpp
+++ b/test/t/io/test_writer_with_mock_compression.cpp
@@ -66,7 +66,7 @@ TEST_CASE("Write with mock compressor") {
             osmium::io::Writer writer("test-writer-mock-fail-on-construction.osm.gz", header, osmium::io::overwrite::allow);
             writer(std::move(buffer));
             writer.close();
-        }(), std::logic_error);
+        }(), const std::logic_error&);
 
     }
 
@@ -78,7 +78,7 @@ TEST_CASE("Write with mock compressor") {
             osmium::io::Writer writer("test-writer-mock-fail-on-write.osm.gz", header, osmium::io::overwrite::allow);
             writer(std::move(buffer));
             writer.close();
-        }(), std::logic_error);
+        }(), const std::logic_error&);
 
     }
 
@@ -90,7 +90,7 @@ TEST_CASE("Write with mock compressor") {
             osmium::io::Writer writer("test-writer-mock-fail-on-close.osm.gz", header, osmium::io::overwrite::allow);
             writer(std::move(buffer));
             writer.close();
-        }(), std::logic_error);
+        }(), const std::logic_error&);
 
     }
 
diff --git a/test/t/io/test_writer_with_mock_encoder.cpp b/test/t/io/test_writer_with_mock_encoder.cpp
index 8a0de76..f57ddbd 100644
--- a/test/t/io/test_writer_with_mock_encoder.cpp
+++ b/test/t/io/test_writer_with_mock_encoder.cpp
@@ -17,8 +17,8 @@ class MockOutputFormat : public osmium::io::detail::OutputFormat {
 
 public:
 
-    MockOutputFormat(const osmium::io::File&, osmium::io::detail::future_string_queue_type& output_queue, const std::string& fail_in) :
-        OutputFormat(output_queue),
+    MockOutputFormat(osmium::thread::Pool& pool, const osmium::io::File&, osmium::io::detail::future_string_queue_type& output_queue, const std::string& fail_in) :
+        OutputFormat(pool, output_queue),
         m_fail_in(fail_in) {
     }
 
@@ -51,8 +51,8 @@ TEST_CASE("Test Writer with MockOutputFormat") {
 
     osmium::io::detail::OutputFormatFactory::instance().register_output_format(
         osmium::io::file_format::xml,
-        [&](const osmium::io::File& file, osmium::io::detail::future_string_queue_type& output_queue) {
-            return new MockOutputFormat{file, output_queue, fail_in};
+        [&](osmium::thread::Pool& pool, const osmium::io::File& file, osmium::io::detail::future_string_queue_type& output_queue) {
+            return new MockOutputFormat{pool, file, output_queue, fail_in};
     });
 
     osmium::io::Header header;
@@ -72,7 +72,7 @@ TEST_CASE("Test Writer with MockOutputFormat") {
             osmium::io::Writer writer("test-writer-mock-fail-on-construction.osm", header, osmium::io::overwrite::allow);
             writer(std::move(buffer));
             writer.close();
-        }(), std::logic_error);
+        }(), const std::logic_error&);
 
     }
 
@@ -84,7 +84,7 @@ TEST_CASE("Test Writer with MockOutputFormat") {
             osmium::io::Writer writer("test-writer-mock-fail-on-construction.osm", header, osmium::io::overwrite::allow);
             writer(std::move(buffer));
             writer.close();
-        }(), std::logic_error);
+        }(), const std::logic_error&);
 
     }
 
@@ -96,7 +96,7 @@ TEST_CASE("Test Writer with MockOutputFormat") {
             osmium::io::Writer writer("test-writer-mock-fail-on-construction.osm", header, osmium::io::overwrite::allow);
             writer(std::move(buffer));
             writer.close();
-        }(), std::logic_error);
+        }(), const std::logic_error&);
 
     }
 
diff --git a/test/t/memory/test_buffer_basics.cpp b/test/t/memory/test_buffer_basics.cpp
index 9e230de..87c4d46 100644
--- a/test/t/memory/test_buffer_basics.cpp
+++ b/test/t/memory/test_buffer_basics.cpp
@@ -1,16 +1,18 @@
 #include "catch.hpp"
 
+#include <array>
+#include <stdexcept>
+
 #include <osmium/memory/buffer.hpp>
 
 TEST_CASE("Buffer basics") {
-
     osmium::memory::Buffer invalid_buffer1;
     osmium::memory::Buffer invalid_buffer2;
     osmium::memory::Buffer empty_buffer1{1024};
     osmium::memory::Buffer empty_buffer2{2048};
 
-    REQUIRE(!invalid_buffer1);
-    REQUIRE(!invalid_buffer2);
+    REQUIRE_FALSE(invalid_buffer1);
+    REQUIRE_FALSE(invalid_buffer2);
     REQUIRE(empty_buffer1);
     REQUIRE(empty_buffer2);
 
@@ -21,15 +23,19 @@ TEST_CASE("Buffer basics") {
     REQUIRE(invalid_buffer1.capacity()  == 0);
     REQUIRE(invalid_buffer1.written()   == 0);
     REQUIRE(invalid_buffer1.committed() == 0);
+    REQUIRE(invalid_buffer1.clear() == 0);
 
     REQUIRE(empty_buffer1.capacity()  == 1024);
     REQUIRE(empty_buffer1.written()   ==    0);
     REQUIRE(empty_buffer1.committed() ==    0);
+    REQUIRE(empty_buffer1.is_aligned());
+    REQUIRE(empty_buffer1.clear() == 0);
 
     REQUIRE(empty_buffer2.capacity()  == 2048);
     REQUIRE(empty_buffer2.written()   ==    0);
     REQUIRE(empty_buffer2.committed() ==    0);
-
+    REQUIRE(empty_buffer2.is_aligned());
+    REQUIRE(empty_buffer2.clear() == 0);
 }
 
 TEST_CASE("Buffer with zero size") {
@@ -52,3 +58,74 @@ TEST_CASE("Buffer with non-aligned size") {
     REQUIRE(buffer.capacity() > 65);
 }
 
+TEST_CASE("Grow a buffer") {
+    osmium::memory::Buffer buffer{128};
+    REQUIRE(buffer.capacity() == 128);
+    buffer.grow(256);
+    REQUIRE(buffer.capacity() == 256);
+    buffer.grow(257);
+    REQUIRE(buffer.capacity() > 256);
+    REQUIRE(buffer.committed() == 0);
+    REQUIRE(buffer.written() == 0);
+    REQUIRE(buffer.is_aligned());
+}
+
+TEST_CASE("Reserve space in a non-growing buffer") {
+    osmium::memory::Buffer buffer{128, osmium::memory::Buffer::auto_grow::no};
+
+    REQUIRE(buffer.reserve_space(20) != nullptr);
+    REQUIRE(buffer.written() == 20);
+    REQUIRE_THROWS_AS(buffer.reserve_space(1000), const osmium::buffer_is_full&);
+}
+
+TEST_CASE("Reserve space in a growing buffer") {
+    osmium::memory::Buffer buffer{128, osmium::memory::Buffer::auto_grow::yes};
+
+    REQUIRE(buffer.reserve_space(20) != nullptr);
+    REQUIRE(buffer.written() == 20);
+    REQUIRE(buffer.reserve_space(1000) != nullptr);
+    REQUIRE(buffer.written() == 1020);
+}
+
+TEST_CASE("Create buffer from existing data with good alignment works") {
+    std::array<unsigned char, 128> data;
+
+    osmium::memory::Buffer buffer{data.data(), data.size()};
+    REQUIRE(buffer.capacity() == 128);
+    REQUIRE(buffer.committed() == 128);
+}
+
+TEST_CASE("Create buffer from existing data with good alignment and committed value works") {
+    std::array<unsigned char, 128> data;
+
+    osmium::memory::Buffer buffer{data.data(), data.size(), 32};
+    REQUIRE(buffer.capacity() == 128);
+    REQUIRE(buffer.committed() == 32);
+    REQUIRE(buffer.written() == 32);
+}
+
+TEST_CASE("Create buffer from existing data with bad alignment fails") {
+    std::array<unsigned char, 128> data;
+
+    const auto l1 = [&](){
+        osmium::memory::Buffer buffer{data.data(), 127};
+    };
+
+    const auto l2 = [&](){
+        osmium::memory::Buffer buffer{data.data(), 127, 120};
+    };
+
+    const auto l3 = [&](){
+        osmium::memory::Buffer buffer{data.data(), 128, 127};
+    };
+
+    const auto l4 = [&](){
+        osmium::memory::Buffer buffer{data.data(), 32, 128};
+    };
+
+    REQUIRE_THROWS_AS(l1(), const std::invalid_argument&);
+    REQUIRE_THROWS_AS(l2(), const std::invalid_argument&);
+    REQUIRE_THROWS_AS(l3(), const std::invalid_argument&);
+    REQUIRE_THROWS_AS(l4(), const std::invalid_argument&);
+}
+
diff --git a/test/t/memory/test_buffer_node.cpp b/test/t/memory/test_buffer_node.cpp
index 040cbb8..19718e7 100644
--- a/test/t/memory/test_buffer_node.cpp
+++ b/test/t/memory/test_buffer_node.cpp
@@ -36,7 +36,7 @@ void check_node_2(const osmium::Node& node) {
         REQUIRE(osmium::item_type::tag_list == item.type());
     }
 
-    REQUIRE(!node.tags().empty());
+    REQUIRE_FALSE(node.tags().empty());
     REQUIRE(2 == std::distance(node.tags().begin(), node.tags().end()));
 
     int n = 0;
diff --git a/test/t/memory/test_callback_buffer.cpp b/test/t/memory/test_callback_buffer.cpp
new file mode 100644
index 0000000..3e94a40
--- /dev/null
+++ b/test/t/memory/test_callback_buffer.cpp
@@ -0,0 +1,101 @@
+#include "catch.hpp"
+
+#include <iterator>
+
+#include <osmium/builder/attr.hpp>
+#include <osmium/memory/callback_buffer.hpp>
+
+using namespace osmium::builder::attr;
+
+TEST_CASE("Callback buffer") {
+    osmium::memory::CallbackBuffer cb;
+
+    REQUIRE(cb.buffer().committed() == 0);
+
+    osmium::builder::add_node(cb.buffer(), _id(1));
+    osmium::builder::add_node(cb.buffer(), _id(2));
+    osmium::builder::add_node(cb.buffer(), _id(3));
+
+    auto c = cb.buffer().committed();
+    REQUIRE(c > 0);
+
+    REQUIRE(std::distance(cb.buffer().begin(), cb.buffer().end()) == 3);
+    auto buffer = cb.read();
+
+    REQUIRE(cb.buffer().committed() == 0);
+    REQUIRE(buffer.committed() == c);
+    REQUIRE(std::distance(cb.buffer().begin(), cb.buffer().end()) == 0);
+
+    // no callback defined, so nothing will happen
+    cb.flush();
+}
+
+TEST_CASE("Callback buffer with callback triggering every time") {
+    int run = 0;
+
+    osmium::memory::CallbackBuffer cb{[&](osmium::memory::Buffer&& buffer){
+        REQUIRE(buffer.committed() > 0);
+        REQUIRE(std::distance(buffer.begin(), buffer.end()) == 1);
+        ++run;
+    }, 1000, 10};
+
+    osmium::builder::add_node(cb.buffer(), _id(1));
+    REQUIRE(cb.buffer().committed() > 10);
+    cb.possibly_flush();
+    osmium::builder::add_node(cb.buffer(), _id(2));
+    REQUIRE(cb.buffer().committed() > 10);
+    cb.possibly_flush();
+    osmium::builder::add_node(cb.buffer(), _id(3));
+    REQUIRE(cb.buffer().committed() > 10);
+    cb.possibly_flush();
+
+    REQUIRE(run == 3);
+    REQUIRE(std::distance(cb.buffer().begin(), cb.buffer().end()) == 0);
+}
+
+TEST_CASE("Callback buffer with callback triggering sometimes") {
+    int run = 0;
+
+    osmium::memory::CallbackBuffer cb{[&](osmium::memory::Buffer&& buffer){
+        REQUIRE(buffer.committed() > 0);
+        ++run;
+    }, 1000, 100};
+
+    osmium::builder::add_node(cb.buffer(), _id(1));
+    REQUIRE(cb.buffer().committed() < 100);
+    cb.possibly_flush();
+    osmium::builder::add_node(cb.buffer(), _id(2));
+    cb.possibly_flush();
+    osmium::builder::add_node(cb.buffer(), _id(3));
+    cb.possibly_flush();
+
+    REQUIRE(run < 3);
+}
+
+TEST_CASE("Callback buffer with callback set later") {
+    int run = 0;
+
+    osmium::memory::CallbackBuffer cb{1000, 10};
+
+    osmium::builder::add_node(cb.buffer(), _id(1));
+    cb.possibly_flush();
+    osmium::builder::add_node(cb.buffer(), _id(2));
+    cb.possibly_flush();
+    osmium::builder::add_node(cb.buffer(), _id(3));
+
+    cb.set_callback([&](osmium::memory::Buffer&& buffer){
+        REQUIRE(buffer.committed() > 0);
+        REQUIRE(std::distance(buffer.begin(), buffer.end()) == 3);
+        ++run;
+    });
+
+    REQUIRE(std::distance(cb.buffer().begin(), cb.buffer().end()) == 3);
+
+    cb.possibly_flush();
+
+    REQUIRE(std::distance(cb.buffer().begin(), cb.buffer().end()) == 0);
+
+    REQUIRE(run == 1);
+}
+
+
diff --git a/test/t/memory/test_item.cpp b/test/t/memory/test_item.cpp
new file mode 100644
index 0000000..a81003d
--- /dev/null
+++ b/test/t/memory/test_item.cpp
@@ -0,0 +1,25 @@
+#include "catch.hpp"
+
+#include <osmium/memory/item.hpp>
+
+TEST_CASE("padded length") {
+    REQUIRE(osmium::memory::padded_length(0) ==  0);
+    REQUIRE(osmium::memory::padded_length(1) ==  8);
+    REQUIRE(osmium::memory::padded_length(2) ==  8);
+    REQUIRE(osmium::memory::padded_length(7) ==  8);
+    REQUIRE(osmium::memory::padded_length(8) ==  8);
+    REQUIRE(osmium::memory::padded_length(9) == 16);
+
+    REQUIRE(osmium::memory::padded_length(2147483647) == 2147483648);
+    REQUIRE(osmium::memory::padded_length(2147483648) == 2147483648);
+    REQUIRE(osmium::memory::padded_length(2147483650) == 2147483656);
+
+    REQUIRE(osmium::memory::padded_length(4294967295) == 4294967296);
+    REQUIRE(osmium::memory::padded_length(4294967296) == 4294967296);
+    REQUIRE(osmium::memory::padded_length(4294967297) == 4294967304);
+
+    REQUIRE(osmium::memory::padded_length(7999999999) == 8000000000);
+    REQUIRE(osmium::memory::padded_length(8000000000) == 8000000000);
+    REQUIRE(osmium::memory::padded_length(8000000001) == 8000000008);
+}
+
diff --git a/test/t/osm/test_area.cpp b/test/t/osm/test_area.cpp
index c964dbb..4451477 100644
--- a/test/t/osm/test_area.cpp
+++ b/test/t/osm/test_area.cpp
@@ -75,7 +75,7 @@ TEST_CASE("Build area") {
     crc32.update(area);
     REQUIRE(crc32().checksum() == 0x2b2b7fa0);
 
-    osmium::Box envelope = area.envelope();
+    const osmium::Box envelope = area.envelope();
     REQUIRE(envelope.bottom_left().lon() == Approx(3.2));
     REQUIRE(envelope.bottom_left().lat() == Approx(4.2));
     REQUIRE(envelope.top_right().lon() == Approx(3.6));
diff --git a/test/t/osm/test_box.cpp b/test/t/osm/test_box.cpp
index 2b7fb3d..179ae09 100644
--- a/test/t/osm/test_box.cpp
+++ b/test/t/osm/test_box.cpp
@@ -8,127 +8,165 @@
 #include <osmium/osm/crc.hpp>
 #include <osmium/geom/relations.hpp>
 
-TEST_CASE("Starting with default constructed box") {
+TEST_CASE("Default constructor creates invalid box") {
+    const osmium::Box b;
 
+    REQUIRE_FALSE(b);
+    REQUIRE_FALSE(b.bottom_left());
+    REQUIRE_FALSE(b.top_right());
+    REQUIRE_THROWS_AS(b.size(), const osmium::invalid_location&);
+}
+
+TEST_CASE("Extend box with undefined") {
+    osmium::Box b;
+
+    REQUIRE_FALSE(b);
+    b.extend(osmium::Location{});
+    REQUIRE_FALSE(b);
+    REQUIRE_FALSE(b.bottom_left());
+    REQUIRE_FALSE(b.top_right());
+}
+
+TEST_CASE("Extend box with invalid") {
     osmium::Box b;
 
-    SECTION("default constructor creates invalid box") {
-        REQUIRE(!b);
-        REQUIRE(!b.bottom_left());
-        REQUIRE(!b.top_right());
-        REQUIRE_THROWS_AS(b.size(), osmium::invalid_location);
-    }
-
-    SECTION("extend with undefined") {
-        REQUIRE(!b);
-        b.extend(osmium::Location{});
-        REQUIRE(!b);
-        REQUIRE(!b.bottom_left());
-        REQUIRE(!b.top_right());
-    }
-
-    SECTION("extend with invalid") {
-        REQUIRE(!b);
-        b.extend(osmium::Location{200.0, 100.0});
-        REQUIRE(!b);
-        REQUIRE(!b.bottom_left());
-        REQUIRE(!b.top_right());
-    }
-
-    SECTION("extend with valid") {
-        osmium::Location loc1 { 1.2, 3.4 };
-        b.extend(loc1);
-        REQUIRE(!!b);
-        REQUIRE(!!b.bottom_left());
-        REQUIRE(!!b.top_right());
-        REQUIRE(b.contains(loc1));
-
-        osmium::Location loc2 { 3.4, 4.5 };
-        osmium::Location loc3 { 5.6, 7.8 };
-
-        b.extend(loc2);
-        b.extend(loc3);
-        REQUIRE(b.bottom_left() == osmium::Location(1.2, 3.4));
-        REQUIRE(b.top_right() == osmium::Location(5.6, 7.8));
-
-        // extend with undefined doesn't change anything
-        b.extend(osmium::Location());
-        REQUIRE(b.bottom_left() == osmium::Location(1.2, 3.4));
-        REQUIRE(b.top_right() == osmium::Location(5.6, 7.8));
-
-        REQUIRE(b.contains(loc1));
-        REQUIRE(b.contains(loc2));
-        REQUIRE(b.contains(loc3));
-
-        osmium::CRC<boost::crc_32_type> crc32;
-        crc32.update(b);
-        REQUIRE(crc32().checksum() == 0xd381a838);
-    }
-
-    SECTION("output defined") {
-        b.extend(osmium::Location(1.2, 3.4));
-        b.extend(osmium::Location(5.6, 7.8));
-        std::stringstream out;
-        out << b;
-        REQUIRE(out.str() == "(1.2,3.4,5.6,7.8)");
-        REQUIRE(b.size() == Approx(19.36).epsilon(0.000001));
-    }
-
-    SECTION("output undefined") {
-        std::stringstream out;
-        out << b;
-        REQUIRE(out.str() == "(undefined)");
-    }
-
-    SECTION("output undefined bottom left") {
-        b.top_right() = osmium::Location(1.2, 3.4);
-        std::stringstream out;
-        out << b;
-        REQUIRE(out.str() == "(undefined)");
-    }
-
-    SECTION("output undefined top right") {
-        b.bottom_left() = osmium::Location(1.2, 3.4);
-        std::stringstream out;
-        out << b;
-        REQUIRE(out.str() == "(undefined)");
-    }
+    REQUIRE_FALSE(b);
+    b.extend(osmium::Location{200.0, 100.0});
+    REQUIRE_FALSE(b);
+    REQUIRE_FALSE(b.bottom_left());
+    REQUIRE_FALSE(b.top_right());
+}
 
+TEST_CASE("Extend box with valid") {
+    osmium::Box b;
+
+    const osmium::Location loc1{1.2, 3.4};
+    b.extend(loc1);
+    REQUIRE(!!b);
+    REQUIRE(!!b.bottom_left());
+    REQUIRE(!!b.top_right());
+    REQUIRE(b.contains(loc1));
+
+    const osmium::Location loc2{3.4, 4.5};
+    const osmium::Location loc3{5.6, 7.8};
+
+    b.extend(loc2);
+    b.extend(loc3);
+    REQUIRE(b.bottom_left() == osmium::Location(1.2, 3.4));
+    REQUIRE(b.top_right() == osmium::Location(5.6, 7.8));
+
+    // extend with undefined doesn't change anything
+    b.extend(osmium::Location{});
+    REQUIRE(b.bottom_left() == osmium::Location(1.2, 3.4));
+    REQUIRE(b.top_right() == osmium::Location(5.6, 7.8));
+
+    REQUIRE(b.contains(loc1));
+    REQUIRE(b.contains(loc2));
+    REQUIRE(b.contains(loc3));
+
+    osmium::CRC<boost::crc_32_type> crc32;
+    crc32.update(b);
+    REQUIRE(crc32().checksum() == 0xd381a838);
+}
+
+TEST_CASE("Output of defined Box") {
+    osmium::Box b;
+
+    b.extend(osmium::Location{1.2, 3.4});
+    b.extend(osmium::Location{5.6, 7.8});
+
+    std::stringstream out;
+    out << b;
+
+    REQUIRE(out.str() == "(1.2,3.4,5.6,7.8)");
+    REQUIRE(b.size() == Approx(19.36).epsilon(0.000001));
+}
+
+TEST_CASE("Output of undefined Box") {
+    const osmium::Box b;
+
+    std::stringstream out;
+    out << b;
+
+    REQUIRE(out.str() == "(undefined)");
+}
+
+TEST_CASE("Output of undefined Box (bottom left)") {
+    osmium::Box b;
+
+    b.top_right() = osmium::Location(1.2, 3.4);
+    std::stringstream out;
+    out << b;
+    REQUIRE(out.str() == "(undefined)");
+}
+
+TEST_CASE("Output of undefined Box (top right)") {
+    osmium::Box b;
+
+    b.bottom_left() = osmium::Location(1.2, 3.4);
+    std::stringstream out;
+    out << b;
+    REQUIRE(out.str() == "(undefined)");
 }
 
 TEST_CASE("Create box from locations") {
-    osmium::Box b{osmium::Location{1.23, 2.34}, osmium::Location{3.45, 4.56}};
+    const osmium::Box b{osmium::Location{1.23, 2.34}, osmium::Location{3.45, 4.56}};
     REQUIRE(!!b);
     REQUIRE(b.bottom_left() == (osmium::Location{1.23, 2.34}));
     REQUIRE(b.top_right() == (osmium::Location{3.45, 4.56}));
 }
 
 TEST_CASE("Create box from doubles") {
-    osmium::Box b{1.23, 2.34, 3.45, 4.56};
+    const osmium::Box b{1.23, 2.34, 3.45, 4.56};
     REQUIRE(!!b);
     REQUIRE(b.bottom_left() == (osmium::Location{1.23, 2.34}));
     REQUIRE(b.top_right() == (osmium::Location{3.45, 4.56}));
 }
 
-TEST_CASE("Relationship between boxes") {
+TEST_CASE("Relationship between boxes: contains") {
+    osmium::Box outer;
+    outer.extend(osmium::Location{1, 1});
+    outer.extend(osmium::Location{10, 10});
+
+    osmium::Box inner;
+    inner.extend(osmium::Location{2, 2});
+    inner.extend(osmium::Location{4, 4});
+
+    osmium::Box overlap;
+    overlap.extend(osmium::Location{3, 3});
+    overlap.extend(osmium::Location{5, 5});
 
+    REQUIRE(      osmium::geom::contains(inner, outer));
+    REQUIRE_FALSE(osmium::geom::contains(outer, inner));
+
+    REQUIRE_FALSE(osmium::geom::contains(overlap, inner));
+    REQUIRE_FALSE(osmium::geom::contains(inner, overlap));
+}
+
+TEST_CASE("Relationship between boxes: overlaps") {
     osmium::Box outer;
-    outer.extend(osmium::Location(1, 1));
-    outer.extend(osmium::Location(10, 10));
+    outer.extend(osmium::Location{1, 1});
+    outer.extend(osmium::Location{10, 10});
 
     osmium::Box inner;
-    inner.extend(osmium::Location(2, 2));
-    inner.extend(osmium::Location(4, 4));
+    inner.extend(osmium::Location{2, 2});
+    inner.extend(osmium::Location{4, 4});
 
     osmium::Box overlap;
-    overlap.extend(osmium::Location(3, 3));
-    overlap.extend(osmium::Location(5, 5));
+    overlap.extend(osmium::Location{3, 3});
+    overlap.extend(osmium::Location{5, 5});
+
+    osmium::Box outside;
+    overlap.extend(osmium::Location{30, 30});
+    overlap.extend(osmium::Location{50, 50});
 
-    REQUIRE( osmium::geom::contains(inner, outer));
-    REQUIRE(!osmium::geom::contains(outer, inner));
+    REQUIRE(osmium::geom::overlaps(inner, outer));
+    REQUIRE(osmium::geom::overlaps(outer, inner));
 
-    REQUIRE(!osmium::geom::contains(overlap, inner));
-    REQUIRE(!osmium::geom::contains(inner, overlap));
+    REQUIRE(osmium::geom::overlaps(overlap, inner));
+    REQUIRE(osmium::geom::overlaps(inner, overlap));
 
+    REQUIRE_FALSE(osmium::geom::overlaps(outside, inner));
+    REQUIRE_FALSE(osmium::geom::overlaps(inner, outside));
 }
 
diff --git a/test/t/osm/test_changeset.cpp b/test/t/osm/test_changeset.cpp
index 9be8fbb..0c1fc42 100644
--- a/test/t/osm/test_changeset.cpp
+++ b/test/t/osm/test_changeset.cpp
@@ -38,7 +38,7 @@ TEST_CASE("Build changeset") {
     crc32.update(cs1);
     REQUIRE(crc32().checksum() == 0x502e8c0e);
 
-    auto pos = osmium::builder::add_changeset(buffer,
+    const auto pos = osmium::builder::add_changeset(buffer,
         _cid(43),
         _created_at(time_t(120)),
         _num_changes(21),
diff --git a/test/t/osm/test_crc.cpp b/test/t/osm/test_crc.cpp
index 253c217..97c3c4c 100644
--- a/test/t/osm/test_crc.cpp
+++ b/test/t/osm/test_crc.cpp
@@ -4,65 +4,75 @@
 
 #include <osmium/osm/crc.hpp>
 
-TEST_CASE("CRC of basic datatypes") {
+TEST_CASE("CRC of bool") {
+    osmium::CRC<boost::crc_32_type> crc32;
+
+    crc32.update_bool(true);
+    crc32.update_bool(false);
 
+    REQUIRE(crc32().checksum() == 0x58c223be);
+}
+
+TEST_CASE("CRC of char") {
     osmium::CRC<boost::crc_32_type> crc32;
 
-    SECTION("Bool") {
-        crc32.update_bool(true);
-        crc32.update_bool(false);
+    crc32.update_int8('x');
+    crc32.update_int8('y');
 
-        REQUIRE(crc32().checksum() == 0x58c223be);
-    }
+    REQUIRE(crc32().checksum() == 0x8fe62899);
+}
 
-    SECTION("Char") {
-        crc32.update_int8('x');
-        crc32.update_int8('y');
+TEST_CASE("CRC of int16") {
+    osmium::CRC<boost::crc_32_type> crc32;
 
-        REQUIRE(crc32().checksum() == 0x8fe62899);
-    }
+    crc32.update_int16(0x0123U);
+    crc32.update_int16(0x1234U);
 
-    SECTION("Int16") {
-        crc32.update_int16(0x0123U);
-        crc32.update_int16(0x1234U);
+    REQUIRE(crc32().checksum() == 0xda923744);
+}
 
-        REQUIRE(crc32().checksum() == 0xda923744);
-    }
+TEST_CASE("CRC of int32") {
+    osmium::CRC<boost::crc_32_type> crc32;
 
-    SECTION("Int32") {
-        crc32.update_int32(0x01234567UL);
-        crc32.update_int32(0x12345678UL);
+    crc32.update_int32(0x01234567UL);
+    crc32.update_int32(0x12345678UL);
 
-        REQUIRE(crc32().checksum() == 0x9b4e2af3);
-    }
+    REQUIRE(crc32().checksum() == 0x9b4e2af3);
+}
 
-    SECTION("Int64") {
-        crc32.update_int64(0x0123456789abcdefULL);
-        crc32.update_int64(0x123456789abcdef0ULL);
+TEST_CASE("CRC of int64") {
+    osmium::CRC<boost::crc_32_type> crc32;
 
-        REQUIRE(crc32().checksum() == 0x6d8b7267);
-    }
+    crc32.update_int64(0x0123456789abcdefULL);
+    crc32.update_int64(0x123456789abcdef0ULL);
 
-    SECTION("String") {
-        const char* str = "foobar";
-        crc32.update_string(str);
+    REQUIRE(crc32().checksum() == 0x6d8b7267);
+}
 
-        REQUIRE(crc32().checksum() == 0x9ef61f95);
-    }
+TEST_CASE("CRC of string") {
+    osmium::CRC<boost::crc_32_type> crc32;
 
-    SECTION("Timestamp") {
-        osmium::Timestamp t("2015-07-12T13:10:46Z");
-        crc32.update(t);
+    const char* str = "foobar";
+    crc32.update_string(str);
 
-        REQUIRE(crc32().checksum() == 0x58a29d7);
-    }
+    REQUIRE(crc32().checksum() == 0x9ef61f95);
+}
+
+TEST_CASE("CRC of Timestamp") {
+    osmium::CRC<boost::crc_32_type> crc32;
 
-    SECTION("Location") {
-        osmium::Location loc { 3.46, 2.001 };
-        crc32.update(loc);
+    const osmium::Timestamp t{"2015-07-12T13:10:46Z"};
+    crc32.update(t);
+
+    REQUIRE(crc32().checksum() == 0x58a29d7);
+}
+
+TEST_CASE("CRC of Location") {
+    osmium::CRC<boost::crc_32_type> crc32;
 
-        REQUIRE(crc32().checksum() == 0xddee042c);
-    }
+    const osmium::Location loc{3.46, 2.001};
+    crc32.update(loc);
 
+    REQUIRE(crc32().checksum() == 0xddee042c);
 }
 
diff --git a/test/t/osm/test_location.cpp b/test/t/osm/test_location.cpp
index 0adfc95..28cf16e 100644
--- a/test/t/osm/test_location.cpp
+++ b/test/t/osm/test_location.cpp
@@ -1,155 +1,156 @@
 #include "catch.hpp"
 
+#include <limits>
 #include <sstream>
 #include <type_traits>
 
 #include <osmium/osm/location.hpp>
 
-TEST_CASE("Location") {
-
 // fails on MSVC and doesn't really matter
 // static_assert(std::is_literal_type<osmium::Location>::value, "osmium::Location not literal type");
 
-    SECTION("instantiation_with_default_parameters") {
-        osmium::Location loc;
-        REQUIRE(!loc);
-        REQUIRE_THROWS_AS(loc.lon(), osmium::invalid_location);
-        REQUIRE_THROWS_AS(loc.lat(), osmium::invalid_location);
-    }
-
-    SECTION("instantiation_with_double_parameters") {
-        osmium::Location loc1(1.2, 4.5);
-        REQUIRE(!!loc1);
-        REQUIRE(12000000 == loc1.x());
-        REQUIRE(45000000 == loc1.y());
-        REQUIRE(1.2 == loc1.lon());
-        REQUIRE(4.5 == loc1.lat());
-
-        osmium::Location loc2(loc1);
-        REQUIRE(4.5 == loc2.lat());
-
-        osmium::Location loc3 = loc1;
-        REQUIRE(4.5 == loc3.lat());
-    }
+TEST_CASE("Location instantiation with default parameters") {
+    const osmium::Location loc;
+    REQUIRE_FALSE(loc);
+    REQUIRE_FALSE(loc.is_defined());
+    REQUIRE(loc.is_undefined());
+    REQUIRE_THROWS_AS(loc.lon(), const osmium::invalid_location&);
+    REQUIRE_THROWS_AS(loc.lat(), const osmium::invalid_location&);
+}
 
-    SECTION("instantiation_with_double_parameters_constructor_with_universal_initializer") {
-        osmium::Location loc { 2.2, 3.3 };
-        REQUIRE(2.2 == loc.lon());
-        REQUIRE(3.3 == loc.lat());
-    }
+TEST_CASE("Location instantiation with double parameters") {
+    const osmium::Location loc1{1.2, 4.5};
+    REQUIRE(bool(loc1));
+    REQUIRE(loc1.is_defined());
+    REQUIRE_FALSE(loc1.is_undefined());
+    REQUIRE(12000000 == loc1.x());
+    REQUIRE(45000000 == loc1.y());
+    REQUIRE(1.2 == loc1.lon());
+    REQUIRE(4.5 == loc1.lat());
+
+    const osmium::Location loc2{loc1};
+    REQUIRE(4.5 == loc2.lat());
+
+    const osmium::Location loc3 = loc1;
+    REQUIRE(4.5 == loc3.lat());
+}
 
-    SECTION("instantiation_with_double_parameters_constructor_with_initializer_list") {
-        osmium::Location loc({ 4.4, 5.5 });
-        REQUIRE(4.4 == loc.lon());
-        REQUIRE(5.5 == loc.lat());
-    }
+TEST_CASE("Location instantiation with double parameters constructor with universal initializer") {
+    const osmium::Location loc{2.2, 3.3};
+    REQUIRE(2.2 == loc.lon());
+    REQUIRE(3.3 == loc.lat());
+}
 
-    SECTION("instantiation_with_double_parameters_operator_equal") {
-        osmium::Location loc = { 5.5, 6.6 };
-        REQUIRE(5.5 == loc.lon());
-        REQUIRE(6.6 == loc.lat());
-    }
+TEST_CASE("Location instantiation with double parameters constructor with initializer list") {
+    const osmium::Location loc({4.4, 5.5});
+    REQUIRE(4.4 == loc.lon());
+    REQUIRE(5.5 == loc.lat());
+}
 
-    SECTION("equality") {
-        osmium::Location loc1(1.2, 4.5);
-        osmium::Location loc2(1.2, 4.5);
-        osmium::Location loc3(1.5, 1.5);
-        REQUIRE(loc1 == loc2);
-        REQUIRE(loc1 != loc3);
-    }
+TEST_CASE("Location instantiation with double parameters operator equal") {
+    const osmium::Location loc = {5.5, 6.6};
+    REQUIRE(5.5 == loc.lon());
+    REQUIRE(6.6 == loc.lat());
+}
 
-    SECTION("order") {
-        REQUIRE(osmium::Location(-1.2, 10.0) < osmium::Location(1.2, 10.0));
-        REQUIRE(osmium::Location(1.2, 10.0) > osmium::Location(-1.2, 10.0));
+TEST_CASE("Location equality") {
+    const osmium::Location loc1{1.2, 4.5};
+    const osmium::Location loc2{1.2, 4.5};
+    const osmium::Location loc3{1.5, 1.5};
+    REQUIRE(loc1 == loc2);
+    REQUIRE(loc1 != loc3);
+}
 
-        REQUIRE(osmium::Location(10.2, 20.0) < osmium::Location(11.2, 20.2));
-        REQUIRE(osmium::Location(10.2, 20.2) < osmium::Location(11.2, 20.0));
-        REQUIRE(osmium::Location(11.2, 20.2) > osmium::Location(10.2, 20.0));
-    }
+TEST_CASE("Location order") {
+    REQUIRE(osmium::Location(-1.2, 10.0) < osmium::Location(1.2, 10.0));
+    REQUIRE(osmium::Location(1.2, 10.0) > osmium::Location(-1.2, 10.0));
 
-    SECTION("validity") {
-        REQUIRE(osmium::Location(0.0, 0.0).valid());
-        REQUIRE(osmium::Location(1.2, 4.5).valid());
-        REQUIRE(osmium::Location(-1.2, 4.5).valid());
-        REQUIRE(osmium::Location(-180.0, -90.0).valid());
-        REQUIRE(osmium::Location(180.0, -90.0).valid());
-        REQUIRE(osmium::Location(-180.0, 90.0).valid());
-        REQUIRE(osmium::Location(180.0, 90.0).valid());
-
-        REQUIRE(!osmium::Location(200.0, 4.5).valid());
-        REQUIRE(!osmium::Location(-1.2, -100.0).valid());
-        REQUIRE(!osmium::Location(-180.0, 90.005).valid());
-    }
+    REQUIRE(osmium::Location(10.2, 20.0) < osmium::Location(11.2, 20.2));
+    REQUIRE(osmium::Location(10.2, 20.2) < osmium::Location(11.2, 20.0));
+    REQUIRE(osmium::Location(11.2, 20.2) > osmium::Location(10.2, 20.0));
+}
 
+TEST_CASE("Location validity") {
+    REQUIRE(osmium::Location(0.0, 0.0).valid());
+    REQUIRE(osmium::Location(1.2, 4.5).valid());
+    REQUIRE(osmium::Location(-1.2, 4.5).valid());
+    REQUIRE(osmium::Location(-180.0, -90.0).valid());
+    REQUIRE(osmium::Location(180.0, -90.0).valid());
+    REQUIRE(osmium::Location(-180.0, 90.0).valid());
+    REQUIRE(osmium::Location(180.0, 90.0).valid());
+
+    REQUIRE_FALSE(osmium::Location(200.0, 4.5).valid());
+    REQUIRE_FALSE(osmium::Location(-1.2, -100.0).valid());
+    REQUIRE_FALSE(osmium::Location(-180.0, 90.005).valid());
+}
 
-    SECTION("output_to_iterator_comma_separator") {
-        char buffer[100];
-        osmium::Location loc(-3.2, 47.3);
-        *loc.as_string(buffer, ',') = 0;
-        REQUIRE(std::string("-3.2,47.3") == buffer);
-    }
 
-    SECTION("output_to_iterator_space_separator") {
-        char buffer[100];
-        osmium::Location loc(0.0, 7.0);
-        *loc.as_string(buffer, ' ') = 0;
-        REQUIRE(std::string("0 7") == buffer);
-    }
+TEST_CASE("Location output to iterator comma separator") {
+    char buffer[100];
+    const osmium::Location loc{-3.2, 47.3};
+    *loc.as_string(buffer, ',') = 0;
+    REQUIRE(std::string("-3.2,47.3") == buffer);
+}
 
-    SECTION("output_to_iterator_check_precision") {
-        char buffer[100];
-        osmium::Location loc(-179.9999999, -90.0);
-        *loc.as_string(buffer, ' ') = 0;
-        REQUIRE(std::string("-179.9999999 -90") == buffer);
-    }
+TEST_CASE("Location output to iterator space separator") {
+    char buffer[100];
+    const osmium::Location loc{0.0, 7.0};
+    *loc.as_string(buffer, ' ') = 0;
+    REQUIRE(std::string("0 7") == buffer);
+}
 
-    SECTION("output_to_iterator_undefined_location") {
-        char buffer[100];
-        osmium::Location loc;
-        REQUIRE_THROWS_AS(loc.as_string(buffer, ','), osmium::invalid_location);
-    }
+TEST_CASE("Location output to iterator check precision") {
+    char buffer[100];
+    const osmium::Location loc{-179.9999999, -90.0};
+    *loc.as_string(buffer, ' ') = 0;
+    REQUIRE(std::string("-179.9999999 -90") == buffer);
+}
 
-    SECTION("output_to_string_comman_separator") {
-        std::string s;
-        osmium::Location loc(-3.2, 47.3);
-        loc.as_string(std::back_inserter(s), ',');
-        REQUIRE(s == "-3.2,47.3");
-    }
+TEST_CASE("Location output to iterator undefined location") {
+    char buffer[100];
+    const osmium::Location loc;
+    REQUIRE_THROWS_AS(loc.as_string(buffer, ','), const osmium::invalid_location&);
+}
 
-    SECTION("output_to_string_space_separator") {
-        std::string s;
-        osmium::Location loc(0.0, 7.0);
-        loc.as_string(std::back_inserter(s), ' ');
-        REQUIRE(s == "0 7");
-    }
+TEST_CASE("Location output to string comman separator") {
+    std::string s;
+    const osmium::Location loc{-3.2, 47.3};
+    loc.as_string(std::back_inserter(s), ',');
+    REQUIRE(s == "-3.2,47.3");
+}
 
-    SECTION("output_to_string_check_precision") {
-        std::string s;
-        osmium::Location loc(-179.9999999, -90.0);
-        loc.as_string(std::back_inserter(s), ' ');
-        REQUIRE(s == "-179.9999999 -90");
-    }
+TEST_CASE("Location output to string space separator") {
+    std::string s;
+    const osmium::Location loc{0.0, 7.0};
+    loc.as_string(std::back_inserter(s), ' ');
+    REQUIRE(s == "0 7");
+}
 
-    SECTION("output_to_string_undefined_location") {
-        std::string s;
-        osmium::Location loc;
-        REQUIRE_THROWS_AS(loc.as_string(std::back_inserter(s), ','), osmium::invalid_location);
-    }
+TEST_CASE("Location output to string check precision") {
+    std::string s;
+    const osmium::Location loc{-179.9999999, -90.0};
+    loc.as_string(std::back_inserter(s), ' ');
+    REQUIRE(s == "-179.9999999 -90");
+}
 
-    SECTION("output_defined") {
-        osmium::Location p(-3.20, 47.30);
-        std::stringstream out;
-        out << p;
-        REQUIRE(out.str() == "(-3.2,47.3)");
-    }
+TEST_CASE("Location output to string undefined location") {
+    std::string s;
+    const osmium::Location loc;
+    REQUIRE_THROWS_AS(loc.as_string(std::back_inserter(s), ','), const osmium::invalid_location&);
+}
 
-    SECTION("output_undefined") {
-        osmium::Location p;
-        std::stringstream out;
-        out << p;
-        REQUIRE(out.str() == "(undefined,undefined)");
-    }
+TEST_CASE("Location output defined") {
+    const osmium::Location loc{-3.20, 47.30};
+    std::stringstream out;
+    out << loc;
+    REQUIRE(out.str() == "(-3.2,47.3)");
+}
 
+TEST_CASE("Location output undefined") {
+    const osmium::Location loc;
+    std::stringstream out;
+    out << loc;
+    REQUIRE(out.str() == "(undefined,undefined)");
 }
 
 TEST_CASE("Location hash") {
@@ -188,10 +189,10 @@ void F(const char* s) {
     strm += s;
     const char* x = strm.c_str();
     const char** data = &x;
-    REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location);
+    REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), const osmium::invalid_location&);
     ++x;
     data = &x;
-    REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), osmium::invalid_location);
+    REQUIRE_THROWS_AS(osmium::detail::string_to_location_coordinate(data), const osmium::invalid_location&);
 }
 
 TEST_CASE("Parsing coordinates from strings") {
@@ -260,6 +261,7 @@ TEST_CASE("Parsing coordinates from strings") {
     C("179.9999999",  1799999999);
     C("179.99999999", 1800000000);
     C("200.123",      2001230000);
+    C("214.7483647",  2147483647);
 
     C("8.109E-4" , 8109);
     C("8.1090E-4" , 8109);
@@ -332,6 +334,12 @@ TEST_CASE("Parsing coordinates from strings") {
     C("1.1e2:", 1100000000, ":");
 }
 
+TEST_CASE("Parsing min coordinate from string") {
+    const char* minval = "-214.7483648";
+    const char** data = &minval;
+    REQUIRE(osmium::detail::string_to_location_coordinate(data) == std::numeric_limits<int32_t>::min());
+}
+
 TEST_CASE("Writing zero coordinate into string") {
     std::string buffer;
     osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), 0);
@@ -369,20 +377,41 @@ TEST_CASE("Writing coordinate into string") {
     CW(  40101010, "4.010101");
     CW( 494561234, "49.4561234");
     CW(1799999999, "179.9999999");
+
+    CW(2147483647, "214.7483647");
+}
+
+TEST_CASE("Writing min coordinate into string") {
+    std::string buffer;
+
+    osmium::detail::append_location_coordinate_to_string(std::back_inserter(buffer), std::numeric_limits<int32_t>::min());
+    REQUIRE(buffer == "-214.7483648");
 }
 
 TEST_CASE("set lon/lat from string") {
     osmium::Location loc;
+    REQUIRE(loc.is_undefined());
+    REQUIRE_FALSE(loc.is_defined());
+    REQUIRE_FALSE(loc.valid());
+
     loc.set_lon("1.2");
+    REQUIRE_FALSE(loc.is_undefined());
+    REQUIRE(loc.is_defined());
+    REQUIRE_FALSE(loc.valid());
+
     loc.set_lat("3.4");
+    REQUIRE_FALSE(loc.is_undefined());
+    REQUIRE(loc.is_defined());
+    REQUIRE(loc.valid());
+
     REQUIRE(loc.lon() == Approx(1.2));
     REQUIRE(loc.lat() == Approx(3.4));
 }
 
 TEST_CASE("set lon/lat from string with trailing characters") {
     osmium::Location loc;
-    REQUIRE_THROWS_AS(loc.set_lon("1.2x"), osmium::invalid_location);
-    REQUIRE_THROWS_AS(loc.set_lat("3.4e1 "), osmium::invalid_location);
+    REQUIRE_THROWS_AS(loc.set_lon("1.2x"), const osmium::invalid_location&);
+    REQUIRE_THROWS_AS(loc.set_lat("3.4e1 "), const osmium::invalid_location&);
 }
 
 TEST_CASE("set lon/lat from string with trailing characters using partial") {
diff --git a/test/t/osm/test_node.cpp b/test/t/osm/test_node.cpp
index e5dbe8a..752e6af 100644
--- a/test/t/osm/test_node.cpp
+++ b/test/t/osm/test_node.cpp
@@ -133,7 +133,7 @@ TEST_CASE("Setting attributes from bad data on strings should fail") {
 TEST_CASE("set large id") {
     osmium::memory::Buffer buffer{10000};
 
-    int64_t id = 3000000000l;
+    const int64_t id = 3000000000l;
     osmium::builder::add_node(buffer, _id(id));
 
     osmium::Node& node = buffer.get<osmium::Node>(0);
@@ -164,3 +164,26 @@ TEST_CASE("set tags on node") {
     REQUIRE(std::string{"pub"} == node.get_value_by_key("amenity", "default"));
 }
 
+TEST_CASE("Setting diff flags on node") {
+    osmium::memory::Buffer buffer{1000};
+
+    osmium::builder::add_node(buffer, _id(17));
+
+    osmium::Node& node = buffer.get<osmium::Node>(0);
+
+    REQUIRE(node.diff() == osmium::diff_indicator_type::none);
+    REQUIRE(node.diff_as_char() == '*');
+
+    node.set_diff(osmium::diff_indicator_type::left);
+    REQUIRE(node.diff() == osmium::diff_indicator_type::left);
+    REQUIRE(node.diff_as_char() == '-');
+
+    node.set_diff(osmium::diff_indicator_type::right);
+    REQUIRE(node.diff() == osmium::diff_indicator_type::right);
+    REQUIRE(node.diff_as_char() == '+');
+
+    node.set_diff(osmium::diff_indicator_type::both);
+    REQUIRE(node.diff() == osmium::diff_indicator_type::both);
+    REQUIRE(node.diff_as_char() == ' ');
+}
+
diff --git a/test/t/osm/test_node_ref.cpp b/test/t/osm/test_node_ref.cpp
index 28a28a5..3494d2a 100644
--- a/test/t/osm/test_node_ref.cpp
+++ b/test/t/osm/test_node_ref.cpp
@@ -6,30 +6,30 @@
 #include <osmium/osm/node_ref_list.hpp>
 
 TEST_CASE("Default construct a NodeRef") {
-    osmium::NodeRef node_ref;
+    const osmium::NodeRef node_ref;
     REQUIRE(node_ref.ref() == 0);
     REQUIRE(node_ref.location() == osmium::Location{});
 }
 
 TEST_CASE("Construct a NodeRef with an id") {
-    osmium::NodeRef node_ref{7};
+    const osmium::NodeRef node_ref{7};
     REQUIRE(node_ref.ref() == 7);
 }
 
 TEST_CASE("Equality comparison fo NodeRefs") {
-    osmium::NodeRef node_ref1{7, {1.2, 3.4}};
-    osmium::NodeRef node_ref2{7, {1.4, 3.1}};
-    osmium::NodeRef node_ref3{9, {1.2, 3.4}};
+    const osmium::NodeRef node_ref1{7, {1.2, 3.4}};
+    const osmium::NodeRef node_ref2{7, {1.4, 3.1}};
+    const osmium::NodeRef node_ref3{9, {1.2, 3.4}};
     REQUIRE(node_ref1 == node_ref2);
     REQUIRE(node_ref1 != node_ref3);
-    REQUIRE(!osmium::location_equal()(node_ref1, node_ref2));
-    REQUIRE(!osmium::location_equal()(node_ref2, node_ref3));
-    REQUIRE(osmium::location_equal()(node_ref1, node_ref3));
+    REQUIRE_FALSE(osmium::location_equal()(node_ref1, node_ref2));
+    REQUIRE_FALSE(osmium::location_equal()(node_ref2, node_ref3));
+    REQUIRE(      osmium::location_equal()(node_ref1, node_ref3));
 }
 
 TEST_CASE("Set location on a NodeRef") {
     osmium::NodeRef node_ref{7};
-    REQUIRE(!node_ref.location().valid());
+    REQUIRE_FALSE(node_ref.location().valid());
     REQUIRE(node_ref.location() == osmium::Location());
     node_ref.set_location(osmium::Location(13.5, -7.2));
     REQUIRE(node_ref.location().lon() == 13.5);
@@ -37,10 +37,10 @@ TEST_CASE("Set location on a NodeRef") {
 }
 
 TEST_CASE("Ordering of NodeRefs") {
-    osmium::NodeRef node_ref1{1, {1.0, 3.0}};
-    osmium::NodeRef node_ref2{2, {1.4, 2.9}};
-    osmium::NodeRef node_ref3{3, {1.2, 3.0}};
-    osmium::NodeRef node_ref4{4, {1.2, 3.3}};
+    const osmium::NodeRef node_ref1{1, {1.0, 3.0}};
+    const osmium::NodeRef node_ref2{2, {1.4, 2.9}};
+    const osmium::NodeRef node_ref3{3, {1.2, 3.0}};
+    const osmium::NodeRef node_ref4{4, {1.2, 3.3}};
 
     REQUIRE(node_ref1 < node_ref2);
     REQUIRE(node_ref2 < node_ref3);
@@ -48,10 +48,10 @@ TEST_CASE("Ordering of NodeRefs") {
     REQUIRE(node_ref1 >= node_ref1);
 
     REQUIRE(osmium::location_less()(node_ref1, node_ref2));
-    REQUIRE(!osmium::location_less()(node_ref2, node_ref3));
+    REQUIRE_FALSE(osmium::location_less()(node_ref2, node_ref3));
     REQUIRE(osmium::location_less()(node_ref1, node_ref3));
     REQUIRE(osmium::location_less()(node_ref3, node_ref4));
-    REQUIRE(!osmium::location_less()(node_ref1, node_ref1));
+    REQUIRE_FALSE(osmium::location_less()(node_ref1, node_ref1));
 }
 
 TEST_CASE("WayNodeList") {
@@ -80,7 +80,7 @@ TEST_CASE("WayNodeList") {
         REQUIRE(nrl.size() == 3);
 
         REQUIRE(nrl[1].location() == osmium::Location(0.0, 1.0));
-        nrl[1].set_location(osmium::Location(13.5, -7.2));
+        nrl[1].set_location(osmium::Location{13.5, -7.2});
         REQUIRE(nrl[1].location() == osmium::Location(13.5, -7.2));
     }
 
@@ -100,7 +100,7 @@ TEST_CASE("WayNodeList") {
         REQUIRE(nrl.ends_have_same_id());
         REQUIRE(nrl.ends_have_same_location());
 
-        osmium::Box envelope = nrl.envelope();
+        const osmium::Box envelope = nrl.envelope();
         REQUIRE(envelope.bottom_left().lon() == Approx(0));
         REQUIRE(envelope.bottom_left().lat() == Approx(0));
         REQUIRE(envelope.top_right().lon() == Approx(1));
diff --git a/test/t/osm/test_object_comparisons.cpp b/test/t/osm/test_object_comparisons.cpp
index 42bcbff..27ded91 100644
--- a/test/t/osm/test_object_comparisons.cpp
+++ b/test/t/osm/test_object_comparisons.cpp
@@ -11,6 +11,72 @@
 
 using namespace osmium::builder::attr;
 
+TEST_CASE("Object ID comparisons") {
+    osmium::object_id_type a =   0;
+    osmium::object_id_type b =  -1;
+    osmium::object_id_type c = -10;
+    osmium::object_id_type d = -11;
+    osmium::object_id_type e =   1;
+    osmium::object_id_type f =  11;
+    osmium::object_id_type g =  12;
+
+    REQUIRE_FALSE(osmium::id_order{}(a, a));
+    REQUIRE(osmium::id_order{}(a, b));
+    REQUIRE(osmium::id_order{}(a, c));
+    REQUIRE(osmium::id_order{}(a, d));
+    REQUIRE(osmium::id_order{}(a, e));
+    REQUIRE(osmium::id_order{}(a, f));
+    REQUIRE(osmium::id_order{}(a, g));
+
+    REQUIRE_FALSE(osmium::id_order{}(b, a));
+    REQUIRE_FALSE(osmium::id_order{}(b, b));
+    REQUIRE(osmium::id_order{}(b, c));
+    REQUIRE(osmium::id_order{}(b, d));
+    REQUIRE(osmium::id_order{}(b, e));
+    REQUIRE(osmium::id_order{}(b, f));
+    REQUIRE(osmium::id_order{}(b, g));
+
+    REQUIRE_FALSE(osmium::id_order{}(c, a));
+    REQUIRE_FALSE(osmium::id_order{}(c, b));
+    REQUIRE_FALSE(osmium::id_order{}(c, c));
+    REQUIRE(osmium::id_order{}(c, d));
+    REQUIRE(osmium::id_order{}(c, e));
+    REQUIRE(osmium::id_order{}(c, f));
+    REQUIRE(osmium::id_order{}(c, g));
+
+    REQUIRE_FALSE(osmium::id_order{}(d, a));
+    REQUIRE_FALSE(osmium::id_order{}(d, b));
+    REQUIRE_FALSE(osmium::id_order{}(d, c));
+    REQUIRE_FALSE(osmium::id_order{}(d, d));
+    REQUIRE(osmium::id_order{}(d, e));
+    REQUIRE(osmium::id_order{}(d, f));
+    REQUIRE(osmium::id_order{}(d, g));
+
+    REQUIRE_FALSE(osmium::id_order{}(e, a));
+    REQUIRE_FALSE(osmium::id_order{}(e, b));
+    REQUIRE_FALSE(osmium::id_order{}(e, c));
+    REQUIRE_FALSE(osmium::id_order{}(e, d));
+    REQUIRE_FALSE(osmium::id_order{}(e, e));
+    REQUIRE(osmium::id_order{}(e, f));
+    REQUIRE(osmium::id_order{}(e, g));
+
+    REQUIRE_FALSE(osmium::id_order{}(f, a));
+    REQUIRE_FALSE(osmium::id_order{}(f, b));
+    REQUIRE_FALSE(osmium::id_order{}(f, c));
+    REQUIRE_FALSE(osmium::id_order{}(f, d));
+    REQUIRE_FALSE(osmium::id_order{}(f, e));
+    REQUIRE_FALSE(osmium::id_order{}(f, f));
+    REQUIRE(osmium::id_order{}(f, g));
+
+    REQUIRE_FALSE(osmium::id_order{}(g, a));
+    REQUIRE_FALSE(osmium::id_order{}(g, b));
+    REQUIRE_FALSE(osmium::id_order{}(g, c));
+    REQUIRE_FALSE(osmium::id_order{}(g, d));
+    REQUIRE_FALSE(osmium::id_order{}(g, e));
+    REQUIRE_FALSE(osmium::id_order{}(g, f));
+    REQUIRE_FALSE(osmium::id_order{}(g, g));
+}
+
 TEST_CASE("Node comparisons") {
 
     osmium::memory::Buffer buffer(10 * 1000);
@@ -38,12 +104,12 @@ TEST_CASE("Node comparisons") {
         REQUIRE_FALSE(nodes[0] > nodes[1]);
     }
 
-    SECTION("IDs are ordered by absolute value") {
+    SECTION("IDs are ordered by sign and then absolute value") {
         nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id(  0))));
-        nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id(  1))));
         nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id( -1))));
-        nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id( 10))));
         nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id(-10))));
+        nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id(  1))));
+        nodes.emplace_back(buffer.get<osmium::Node>(osmium::builder::add_node(buffer, _id( 10))));
 
         REQUIRE(std::is_sorted(nodes.cbegin(), nodes.cend()));
     }
diff --git a/test/t/osm/test_timestamp.cpp b/test/t/osm/test_timestamp.cpp
index 561a705..12d8501 100644
--- a/test/t/osm/test_timestamp.cpp
+++ b/test/t/osm/test_timestamp.cpp
@@ -1,79 +1,77 @@
 #include "catch.hpp"
 
 #include <sstream>
+#include <string>
+#include <vector>
 
 #include <osmium/osm/timestamp.hpp>
 
-TEST_CASE("Timestamp") {
-
-    SECTION("can be default initialized to invalid value") {
-        osmium::Timestamp t;
-        REQUIRE(0 == uint32_t(t));
-        REQUIRE("" == t.to_iso());
-        REQUIRE_FALSE(t.valid());
-    }
-
-    SECTION("invalid value is zero") {
-        osmium::Timestamp t(static_cast<time_t>(0));
-        REQUIRE(0 == uint32_t(t));
-        REQUIRE("" == t.to_iso());
-        REQUIRE_FALSE(t.valid());
-    }
+TEST_CASE("Timestamp can be default initialized to invalid value") {
+    const osmium::Timestamp t;
+    REQUIRE(0 == uint32_t(t));
+    REQUIRE("" == t.to_iso());
+    REQUIRE_FALSE(t.valid());
+}
 
-    SECTION("can be initialized from time_t") {
-        osmium::Timestamp t(static_cast<time_t>(1));
-        REQUIRE(1 == uint32_t(t));
-        REQUIRE("1970-01-01T00:00:01Z" == t.to_iso());
-        REQUIRE(t.valid());
-    }
+TEST_CASE("Timestamp invalid value is zero") {
+    const osmium::Timestamp t{static_cast<time_t>(0)};
+    REQUIRE(0 == uint32_t(t));
+    REQUIRE("" == t.to_iso());
+    REQUIRE_FALSE(t.valid());
+}
 
-    SECTION("can be initialized from const char*") {
-        osmium::Timestamp t("2000-01-01T00:00:00Z");
-        REQUIRE("2000-01-01T00:00:00Z" == t.to_iso());
-        REQUIRE(t.valid());
-    }
+TEST_CASE("Timestamp can be initialized from time_t") {
+    const osmium::Timestamp t{static_cast<time_t>(1)};
+    REQUIRE(1 == uint32_t(t));
+    REQUIRE("1970-01-01T00:00:01Z" == t.to_iso());
+    REQUIRE(t.valid());
+}
 
-    SECTION("can be initialized from string") {
-        std::string s = "2000-01-01T00:00:00Z";
-        osmium::Timestamp t(s);
-        REQUIRE("2000-01-01T00:00:00Z" == t.to_iso());
-        REQUIRE(t.valid());
-    }
+TEST_CASE("Timestamp can be initialized from const char*") {
+    const osmium::Timestamp t{"2000-01-01T00:00:00Z"};
+    REQUIRE("2000-01-01T00:00:00Z" == t.to_iso());
+    REQUIRE(t.valid());
+}
 
-    SECTION("throws if initialized from bad string") {
-        REQUIRE_THROWS_AS(osmium::Timestamp("x"), std::invalid_argument);
-    }
+TEST_CASE("Timestamp can be initialized from string") {
+    const std::string s = "2000-01-01T00:00:00Z";
+    const osmium::Timestamp t{s};
+    REQUIRE("2000-01-01T00:00:00Z" == t.to_iso());
+    REQUIRE(t.valid());
+}
 
-    SECTION("can be explicitly cast to time_t") {
-        osmium::Timestamp t(4242);
-        time_t x = t.seconds_since_epoch();
-        REQUIRE(x == 4242);
-    }
+TEST_CASE("Timestamp throws if initialized from bad string") {
+    REQUIRE_THROWS_AS(osmium::Timestamp("x"), const std::invalid_argument&);
+}
 
-    SECTION("uint32_t can be initialized from Timestamp") {
-        osmium::Timestamp t(4242);
-        uint32_t x { t };
+TEST_CASE("Timestamp can be explicitly cast to time_t") {
+    const osmium::Timestamp t{4242};
+    const time_t x = t.seconds_since_epoch();
+    REQUIRE(x == 4242);
+}
 
-        REQUIRE(x == 4242);
-    }
+TEST_CASE("Timestamp uint32_t can be initialized from Timestamp") {
+    const osmium::Timestamp t{4242};
+    const uint32_t x { t };
 
-    SECTION("can be compared") {
-        osmium::Timestamp t1(10);
-        osmium::Timestamp t2(50);
-        REQUIRE(t1 < t2);
-        REQUIRE(t1 > osmium::start_of_time());
-        REQUIRE(t2 > osmium::start_of_time());
-        REQUIRE(t1 < osmium::end_of_time());
-        REQUIRE(t2 < osmium::end_of_time());
-    }
+    REQUIRE(x == 4242);
+}
 
-    SECTION("can be written to stream") {
-        std::stringstream ss;
-        osmium::Timestamp t(1);
-        ss << t;
-        REQUIRE("1970-01-01T00:00:01Z" == ss.str());
-    }
+TEST_CASE("Timestamps can be compared") {
+    const osmium::Timestamp t1{10};
+    const osmium::Timestamp t2{50};
+    REQUIRE(t1 < t2);
+    REQUIRE(t1 > osmium::start_of_time());
+    REQUIRE(t2 > osmium::start_of_time());
+    REQUIRE(t1 < osmium::end_of_time());
+    REQUIRE(t2 < osmium::end_of_time());
+}
 
+TEST_CASE("Timestamp can be written to stream") {
+    const osmium::Timestamp t{1};
+    std::stringstream ss;
+    ss << t;
+    REQUIRE("1970-01-01T00:00:01Z" == ss.str());
 }
 
 TEST_CASE("Valid timestamps") {
@@ -88,34 +86,34 @@ TEST_CASE("Valid timestamps") {
     };
 
     for (const auto& tc : test_cases) {
-        osmium::Timestamp t{tc};
+        const osmium::Timestamp t{tc};
         REQUIRE(tc == t.to_iso());
     }
 
 }
 
 TEST_CASE("Invalid timestamps") {
-    REQUIRE_THROWS_AS(osmium::Timestamp{""}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"x"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"xxxxxxxxxxxxxxxxxxxx"}, std::invalid_argument);
-
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01x00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:00x"}, std::invalid_argument);
-
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000x01-01T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01x01T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00x00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00x00Z"}, std::invalid_argument);
-
-    REQUIRE_THROWS_AS(osmium::Timestamp{"0000-00-01T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-00-01T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-00T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T24:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:60:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:61Z"}, std::invalid_argument);
-
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-32T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-02-30T00:00:00Z"}, std::invalid_argument);
-    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-32T00:00:00Z"}, std::invalid_argument);
+    REQUIRE_THROWS_AS(osmium::Timestamp{""}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"x"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"xxxxxxxxxxxxxxxxxxxx"}, const std::invalid_argument&);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01x00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:00x"}, const std::invalid_argument&);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000x01-01T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01x01T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00x00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00x00Z"}, const std::invalid_argument&);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"0000-00-01T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-00-01T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-00T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T24:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:60:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-01T00:00:61Z"}, const std::invalid_argument&);
+
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-01-32T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-02-30T00:00:00Z"}, const std::invalid_argument&);
+    REQUIRE_THROWS_AS(osmium::Timestamp{"2000-03-32T00:00:00Z"}, const std::invalid_argument&);
 }
 
diff --git a/test/t/osm/test_types_from_string.cpp b/test/t/osm/test_types_from_string.cpp
index 2866b8f..0ea566e 100644
--- a/test/t/osm/test_types_from_string.cpp
+++ b/test/t/osm/test_types_from_string.cpp
@@ -9,68 +9,68 @@ TEST_CASE("set ID from string") {
     REQUIRE(osmium::string_to_object_id("-17") == -17);
     REQUIRE(osmium::string_to_object_id("01") == 1);
 
-    REQUIRE_THROWS_AS(osmium::string_to_object_id(""), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id(" "), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id(" 22"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("x"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("0x1"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("12a"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("12345678901234567890"), std::range_error);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id(""), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id(" "), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id(" 22"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("x"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("0x1"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("12a"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("12345678901234567890"), const std::range_error&);
 }
 
 TEST_CASE("set type and ID from string") {
-    auto n17 = osmium::string_to_object_id("n17", osmium::osm_entity_bits::nwr);
+    const auto n17 = osmium::string_to_object_id("n17", osmium::osm_entity_bits::nwr);
     REQUIRE(n17.first == osmium::item_type::node);
     REQUIRE(n17.second == 17);
 
-    auto w42 = osmium::string_to_object_id("w42", osmium::osm_entity_bits::nwr);
+    const auto w42 = osmium::string_to_object_id("w42", osmium::osm_entity_bits::nwr);
     REQUIRE(w42.first == osmium::item_type::way);
     REQUIRE(w42.second == 42);
 
-    auto r_2 = osmium::string_to_object_id("r-2", osmium::osm_entity_bits::nwr);
+    const auto r_2 = osmium::string_to_object_id("r-2", osmium::osm_entity_bits::nwr);
     REQUIRE(r_2.first == osmium::item_type::relation);
     REQUIRE(r_2.second == -2);
 
-    auto d3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr);
+    const auto d3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr);
     REQUIRE(d3.first == osmium::item_type::undefined);
     REQUIRE(d3.second == 3);
 
-    auto u3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr, osmium::item_type::undefined);
+    const auto u3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr, osmium::item_type::undefined);
     REQUIRE(u3.first == osmium::item_type::undefined);
     REQUIRE(u3.second == 3);
 
-    auto n3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr, osmium::item_type::node);
+    const auto n3 = osmium::string_to_object_id("3", osmium::osm_entity_bits::nwr, osmium::item_type::node);
     REQUIRE(n3.first == osmium::item_type::node);
     REQUIRE(n3.second == 3);
 
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("", osmium::osm_entity_bits::nwr), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("n", osmium::osm_entity_bits::nwr), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("x3", osmium::osm_entity_bits::nwr), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("nx3", osmium::osm_entity_bits::nwr), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("n3", osmium::osm_entity_bits::way), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_id("n3a", osmium::osm_entity_bits::nwr), std::range_error);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("", osmium::osm_entity_bits::nwr), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("n", osmium::osm_entity_bits::nwr), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("x3", osmium::osm_entity_bits::nwr), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("nx3", osmium::osm_entity_bits::nwr), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("n3", osmium::osm_entity_bits::way), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_id("n3a", osmium::osm_entity_bits::nwr), const std::range_error&);
 }
 
 TEST_CASE("set object version from string") {
     REQUIRE(osmium::string_to_object_version("0") == 0);
     REQUIRE(osmium::string_to_object_version("1") == 1);
 
-    REQUIRE_THROWS_AS(osmium::string_to_object_version("-1"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_version(""), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_version(" "), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_version(" 22"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_object_version("x"), std::range_error);
+    REQUIRE_THROWS_AS(osmium::string_to_object_version("-1"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_version(""), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_version(" "), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_version(" 22"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_object_version("x"), const std::range_error&);
 }
 
 TEST_CASE("set changeset id from string") {
     REQUIRE(osmium::string_to_changeset_id("0") == 0);
     REQUIRE(osmium::string_to_changeset_id("1") == 1);
 
-    REQUIRE_THROWS_AS(osmium::string_to_changeset_id("-1"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_changeset_id(""), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_changeset_id(" "), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_changeset_id(" 22"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_changeset_id("x"), std::range_error);
+    REQUIRE_THROWS_AS(osmium::string_to_changeset_id("-1"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_changeset_id(""), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_changeset_id(" "), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_changeset_id(" 22"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_changeset_id("x"), const std::range_error&);
 }
 
 TEST_CASE("set user id from string") {
@@ -78,21 +78,21 @@ TEST_CASE("set user id from string") {
     REQUIRE(osmium::string_to_user_id("1") == 1);
     REQUIRE(osmium::string_to_user_id("-1") == -1);
 
-    REQUIRE_THROWS_AS(osmium::string_to_user_id("-2"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_user_id(""), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_user_id(" "), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_user_id(" 22"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_user_id("x"), std::range_error);
+    REQUIRE_THROWS_AS(osmium::string_to_user_id("-2"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_user_id(""), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_user_id(" "), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_user_id(" 22"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_user_id("x"), const std::range_error&);
 }
 
 TEST_CASE("set num changes from string") {
     REQUIRE(osmium::string_to_num_changes("0") == 0);
     REQUIRE(osmium::string_to_num_changes("1") == 1);
 
-    REQUIRE_THROWS_AS(osmium::string_to_num_changes("-1"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_num_changes(""), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_num_changes(" "), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_num_changes(" 22"), std::range_error);
-    REQUIRE_THROWS_AS(osmium::string_to_num_changes("x"), std::range_error);
+    REQUIRE_THROWS_AS(osmium::string_to_num_changes("-1"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_num_changes(""), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_num_changes(" "), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_num_changes(" 22"), const std::range_error&);
+    REQUIRE_THROWS_AS(osmium::string_to_num_changes("x"), const std::range_error&);
 }
 
diff --git a/test/t/osm/test_way.cpp b/test/t/osm/test_way.cpp
index 699abe1..bbfcfc9 100644
--- a/test/t/osm/test_way.cpp
+++ b/test/t/osm/test_way.cpp
@@ -42,7 +42,7 @@ TEST_CASE("Build way") {
     REQUIRE(1 == way.nodes()[0].ref());
     REQUIRE(3 == way.nodes()[1].ref());
     REQUIRE(2 == way.nodes()[2].ref());
-    REQUIRE(! way.is_closed());
+    REQUIRE_FALSE(way.is_closed());
 
     osmium::CRC<boost::crc_32_type> crc32;
     crc32.update(way);
@@ -92,7 +92,7 @@ TEST_CASE("build way with helpers") {
     REQUIRE(22 == way.nodes()[0].ref());
     REQUIRE(4.1 == Approx(way.nodes()[1].location().lon()));
 
-    osmium::Box envelope = way.envelope();
+    const osmium::Box envelope = way.envelope();
     REQUIRE(envelope.bottom_left().lon() == Approx(3.5));
     REQUIRE(envelope.bottom_left().lat() == Approx(2.2));
     REQUIRE(envelope.top_right().lon() == Approx(4.1));
diff --git a/test/t/relations/data.osm b/test/t/relations/data.osm
new file mode 100644
index 0000000..796b986
--- /dev/null
+++ b/test/t/relations/data.osm
@@ -0,0 +1,32 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="10" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.0"/>
+    <node id="11" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.1"/>
+    <node id="12" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.2"/>
+    <node id="13" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.3"/>
+    <node id="14" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.4"/>
+    <way id="20" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="highway" v="primary"/>
+        <nd ref="10"/>
+        <nd ref="11"/>
+    </way>
+    <way id="21" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="highway" v="residential"/>
+        <nd ref="11"/>
+        <nd ref="12"/>
+    </way>
+    <relation id="30" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="type" v="test"/>
+        <member type="node" ref="10" role="none"/>
+    </relation>
+    <relation id="31" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="type" v="restriction"/>
+        <member type="way" ref="20" role="from"/>
+        <member type="node" ref="11" role="via"/>
+        <member type="way" ref="22" role="to"/>
+    </relation>
+    <relation id="32" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="test" v="relinrel"/>
+        <member type="relation" ref="30" role="none"/>
+    </relation>
+</osm>
diff --git a/test/t/relations/dupl_member.osm b/test/t/relations/dupl_member.osm
new file mode 100644
index 0000000..58683bc
--- /dev/null
+++ b/test/t/relations/dupl_member.osm
@@ -0,0 +1,17 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="testdata" upload="false">
+    <node id="10" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.0"/>
+    <node id="11" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.1"/>
+    <node id="12" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1" lat="1.0" lon="1.1"/>
+    <relation id="30" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="type" v="test"/>
+        <member type="node" ref="10" role="none"/>
+        <member type="node" ref="10" role="none"/>
+        <member type="node" ref="11" role="none"/>
+    </relation>
+    <relation id="31" version="1" timestamp="2014-01-01T00:00:00Z" uid="1" user="test" changeset="1">
+        <tag k="type" v="test"/>
+        <member type="node" ref="10" role="none"/>
+        <member type="node" ref="12" role="none"/>
+    </relation>
+</osm>
diff --git a/test/t/relations/test_members_database.cpp b/test/t/relations/test_members_database.cpp
new file mode 100644
index 0000000..f2a4691
--- /dev/null
+++ b/test/t/relations/test_members_database.cpp
@@ -0,0 +1,194 @@
+#include "catch.hpp"
+
+#include <osmium/builder/attr.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/relations/members_database.hpp>
+#include <osmium/relations/relations_database.hpp>
+#include <osmium/storage/item_stash.hpp>
+
+osmium::memory::Buffer fill_buffer() {
+    using namespace osmium::builder::attr;
+    osmium::memory::Buffer buffer{1024 * 1024, osmium::memory::Buffer::auto_grow::yes};
+
+    osmium::builder::add_relation(buffer,
+        _id(20),
+        _member(osmium::item_type::way, 10, "outer")
+    );
+
+    osmium::builder::add_relation(buffer,
+        _id(21),
+        _member(osmium::item_type::way, 11, "outer"),
+        _member(osmium::item_type::way, 12, "outer")
+    );
+
+    osmium::builder::add_relation(buffer,
+        _id(22),
+        _member(osmium::item_type::way, 13, "outer"),
+        _member(osmium::item_type::way, 10, "inner"),
+        _member(osmium::item_type::way, 14, "inner")
+    );
+
+    osmium::builder::add_way(buffer, _id(10));
+    osmium::builder::add_way(buffer, _id(11));
+    osmium::builder::add_way(buffer, _id(12));
+    osmium::builder::add_way(buffer, _id(13));
+    osmium::builder::add_way(buffer, _id(14));
+    osmium::builder::add_way(buffer, _id(15));
+
+    return buffer;
+}
+
+TEST_CASE("Fill member database") {
+    const auto buffer = fill_buffer();
+
+    osmium::ItemStash stash;
+    osmium::relations::RelationsDatabase rdb{stash};
+    osmium::relations::MembersDatabase<osmium::Way> mdb{stash, rdb};
+
+    REQUIRE(mdb.used_memory() < 100);
+
+    for (const auto& relation : buffer.select<osmium::Relation>()) {
+        auto handle = rdb.add(relation);
+        int n = 0;
+        for (const auto& member : relation.members()) {
+            mdb.track(handle, member.ref(), n);
+            ++n;
+        }
+    }
+
+    mdb.prepare_for_lookup();
+
+    int n = 0;
+    int match = 0;
+    for (const auto& way : buffer.select<osmium::Way>()) {
+        bool added = mdb.add(way, [&](osmium::relations::RelationHandle& rel_handle) {
+            ++match;
+            switch (n) {
+                case 0: // added w10
+                    REQUIRE(rel_handle->id() == 20);
+                    break;
+                case 2: // added w11 and w12
+                    REQUIRE(rel_handle->id() == 21);
+                    break;
+                case 4: // added w13 and w14
+                    REQUIRE(rel_handle->id() == 22);
+                    break;
+                default:
+                    REQUIRE(false);
+                    break;
+            }
+        });
+
+        REQUIRE(added == (way.id() != 15));
+
+        if (way.id() == 11) {
+            const auto* way_ptr = mdb.get(way.id());
+            REQUIRE(way_ptr);
+            REQUIRE(*way_ptr == way);
+            const auto* object = mdb.get_object(way.id());
+            REQUIRE(object);
+            REQUIRE(object->id() == way.id());
+        }
+
+        ++n;
+    }
+
+    REQUIRE(match == 3);
+    REQUIRE(mdb.used_memory() > 100);
+}
+
+TEST_CASE("Member database with duplicate member in relation") {
+    using namespace osmium::builder::attr;
+    osmium::memory::Buffer buffer{1024 * 1024, osmium::memory::Buffer::auto_grow::yes};
+
+    osmium::builder::add_relation(buffer,
+        _id(20),
+        _member(osmium::item_type::way, 10, "outer"),
+        _member(osmium::item_type::way, 11, "inner"),
+        _member(osmium::item_type::way, 12, "inner"),
+        _member(osmium::item_type::way, 11, "inner")
+    );
+
+    osmium::builder::add_way(buffer, _id(10));
+    osmium::builder::add_way(buffer, _id(11));
+    osmium::builder::add_way(buffer, _id(12));
+
+    osmium::ItemStash stash;
+    osmium::relations::RelationsDatabase rdb{stash};
+    osmium::relations::MembersDatabase<osmium::Way> mdb{stash, rdb};
+
+    for (const auto& relation : buffer.select<osmium::Relation>()) {
+        auto handle = rdb.add(relation);
+        int n = 0;
+        for (const auto& member : relation.members()) {
+            mdb.track(handle, member.ref(), n);
+            ++n;
+        }
+    }
+
+    mdb.prepare_for_lookup();
+
+    REQUIRE(mdb.size() == 4);
+    {
+        const auto counts = mdb.count();
+        REQUIRE(counts.tracked   == 4);
+        REQUIRE(counts.available == 0);
+        REQUIRE(counts.removed   == 0);
+    }
+
+    int n = 0;
+    for (const auto& way : buffer.select<osmium::Way>()) {
+        mdb.add(way, [&](osmium::relations::RelationHandle& rel_handle) {
+            ++n;
+            REQUIRE(rel_handle->id() == 20);
+            {
+                const auto counts = mdb.count();
+                REQUIRE(counts.tracked   == 0);
+                REQUIRE(counts.available == 4);
+                REQUIRE(counts.removed   == 0);
+            }
+
+            // relation is complete here, normal code would handle it here
+
+            for (const auto& member : rel_handle->members()) {
+                mdb.remove(member.ref(), rel_handle->id());
+            }
+            rel_handle.remove();
+        });
+    }
+
+    REQUIRE(n == 1);
+
+    REQUIRE(rdb.size() == 1);
+    REQUIRE(rdb.count_relations() == 0);
+
+    REQUIRE(mdb.size() == 4);
+    {
+        const auto counts = mdb.count();
+        REQUIRE(counts.tracked   == 0);
+        REQUIRE(counts.available == 0);
+        REQUIRE(counts.removed   == 4);
+    }
+}
+
+TEST_CASE("Remove non-existing object from members database doesn't do anything") {
+    const auto buffer = fill_buffer();
+
+    osmium::ItemStash stash;
+    osmium::relations::RelationsDatabase rdb{stash};
+    osmium::relations::MembersDatabase<osmium::Way> mdb{stash, rdb};
+
+    for (const auto& relation : buffer.select<osmium::Relation>()) {
+        auto handle = rdb.add(relation);
+        int n = 0;
+        for (const auto& member : relation.members()) {
+            mdb.track(handle, member.ref(), n);
+            ++n;
+        }
+    }
+
+    REQUIRE(mdb.size() == 6);
+    mdb.remove(100, 100);
+    REQUIRE(mdb.size() == 6);
+}
+
diff --git a/test/t/relations/test_read_relations.cpp b/test/t/relations/test_read_relations.cpp
new file mode 100644
index 0000000..5e79133
--- /dev/null
+++ b/test/t/relations/test_read_relations.cpp
@@ -0,0 +1,77 @@
+#include "catch.hpp"
+#include "utils.hpp"
+
+#include <osmium/handler.hpp>
+#include <osmium/io/xml_input.hpp>
+#include <osmium/relations/manager_util.hpp>
+#include <osmium/util/progress_bar.hpp>
+
+class TestHandler : public osmium::handler::Handler {
+
+public:
+
+    int count = 0;
+    bool prep = false;
+
+    void relation(const osmium::Relation&) noexcept {
+        ++count;
+    }
+
+    void prepare_for_lookup() noexcept {
+        prep = true;
+    }
+
+}; // class TestHandler
+
+TEST_CASE("Read relations with one handler") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+
+    TestHandler handler;
+
+    osmium::relations::read_relations(file, handler);
+
+    REQUIRE(handler.count == 3);
+    REQUIRE(handler.prep);
+}
+
+TEST_CASE("Read relations with two handlers") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+
+    TestHandler handler1;
+    TestHandler handler2;
+
+    osmium::relations::read_relations(file, handler1, handler2);
+
+    REQUIRE(handler1.count == 3);
+    REQUIRE(handler2.count == 3);
+    REQUIRE(handler1.prep);
+    REQUIRE(handler2.prep);
+}
+
+TEST_CASE("Read relations with progress bar and one handler") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+    osmium::ProgressBar progress_bar{file.size(), false};
+
+    TestHandler handler;
+
+    osmium::relations::read_relations(progress_bar, file, handler);
+
+    REQUIRE(handler.count == 3);
+    REQUIRE(handler.prep);
+}
+
+TEST_CASE("Read relations with progress bar and two handlers") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+    osmium::ProgressBar progress_bar{file.size(), false};
+
+    TestHandler handler1;
+    TestHandler handler2;
+
+    osmium::relations::read_relations(progress_bar, file, handler1, handler2);
+
+    REQUIRE(handler1.count == 3);
+    REQUIRE(handler2.count == 3);
+    REQUIRE(handler1.prep);
+    REQUIRE(handler2.prep);
+}
+
diff --git a/test/t/relations/test_relations_database.cpp b/test/t/relations/test_relations_database.cpp
new file mode 100644
index 0000000..e352ea5
--- /dev/null
+++ b/test/t/relations/test_relations_database.cpp
@@ -0,0 +1,105 @@
+#include "catch.hpp"
+
+#include <osmium/builder/attr.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/relations/relations_database.hpp>
+#include <osmium/storage/item_stash.hpp>
+
+osmium::memory::Buffer fill_buffer() {
+    using namespace osmium::builder::attr;
+    osmium::memory::Buffer buffer{1024 * 1024, osmium::memory::Buffer::auto_grow::yes};
+
+    osmium::builder::add_relation(buffer,
+        _id(1),
+        _member(osmium::item_type::way, 1, "outer")
+    );
+
+    osmium::builder::add_relation(buffer,
+        _id(2),
+        _member(osmium::item_type::way, 1, "outer"),
+        _member(osmium::item_type::way, 2, "outer")
+    );
+
+    osmium::builder::add_relation(buffer,
+        _id(3),
+        _member(osmium::item_type::way, 1, "outer"),
+        _member(osmium::item_type::way, 2, "inner"),
+        _member(osmium::item_type::way, 3, "inner")
+    );
+
+    return buffer;
+}
+
+TEST_CASE("Fill relation database") {
+    const auto buffer = fill_buffer();
+
+    osmium::ItemStash stash;
+    osmium::relations::RelationsDatabase rdb{stash};
+
+    REQUIRE(rdb.size() == 0);
+    REQUIRE(rdb.used_memory() < 100);
+
+    for (const auto& relation : buffer.select<osmium::Relation>()) {
+        auto handle = rdb.add(relation);
+        handle.set_members(relation.cmembers().size());
+        handle.decrement_members();
+        REQUIRE(handle.has_all_members() == (relation.id() == 1));
+    }
+
+    REQUIRE(rdb.size() == 3);
+
+    int n = 0;
+    rdb.for_each_relation([&](const osmium::relations::RelationHandle& rel_handle) {
+        ++n;
+        REQUIRE(rel_handle->members().size() == (*rel_handle).id());
+    });
+    REQUIRE(n == 3);
+}
+
+TEST_CASE("Check need members and handle ops") {
+    const auto buffer = fill_buffer();
+
+    osmium::ItemStash stash;
+    osmium::relations::RelationsDatabase rdb{stash};
+
+    for (const auto& relation : buffer.select<osmium::Relation>()) {
+        auto handle = rdb.add(relation);
+        REQUIRE(*handle == relation);
+        REQUIRE(handle->id() == relation.id());
+        REQUIRE(handle.pos() + 1 == relation.positive_id());
+        REQUIRE(rdb[handle.pos()].pos() == handle.pos());
+
+        for (auto i = relation.id(); i > 0; --i) {
+            handle.increment_members();
+        }
+
+        handle.decrement_members();
+        REQUIRE(handle.has_all_members() == (relation.id() == 1));
+        if (handle.has_all_members()) {
+            handle.remove();
+        }
+    }
+
+    REQUIRE(rdb.size() == 3);
+
+    std::vector<const osmium::Relation*> rels;
+    rdb.for_each_relation([&](const osmium::relations::RelationHandle& rel_handle) {
+        rels.push_back(&*rel_handle);
+    });
+
+    REQUIRE(rels.size() == 2);
+
+    osmium::object_id_type n = 2;
+    for (const auto* rel : rels) {
+        REQUIRE(rel->id() == n);
+        ++n;
+    }
+
+    REQUIRE(rdb[1]->id() == 2);
+    REQUIRE(rdb[2]->id() == 3);
+
+    rdb[1].remove();
+
+    REQUIRE(rdb.count_relations() == 1);
+}
+
diff --git a/test/t/relations/test_relations_manager.cpp b/test/t/relations/test_relations_manager.cpp
new file mode 100644
index 0000000..d7ea12f
--- /dev/null
+++ b/test/t/relations/test_relations_manager.cpp
@@ -0,0 +1,248 @@
+#include "catch.hpp"
+#include "utils.hpp"
+
+#include <osmium/io/xml_input.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/relations/relations_manager.hpp>
+
+struct EmptyRM : public osmium::relations::RelationsManager<EmptyRM, true, true, true> {
+};
+
+struct TestRM : public osmium::relations::RelationsManager<TestRM, true, true, true> {
+
+    std::size_t count_new_rels      = 0;
+    std::size_t count_new_members   = 0;
+    std::size_t count_complete_rels = 0;
+    std::size_t count_before        = 0;
+    std::size_t count_not_in_any    = 0;
+    std::size_t count_after         = 0;
+
+    bool new_relation(const osmium::Relation& /*relation*/) noexcept {
+        ++count_new_rels;
+        return true;
+    }
+
+    bool new_member(const osmium::Relation& /*relation*/, const osmium::RelationMember& /*member*/, std::size_t /*n*/) noexcept {
+        ++count_new_members;
+        return true;
+    }
+
+    void complete_relation(const osmium::Relation& /*relation*/) noexcept {
+        ++count_complete_rels;
+    }
+
+    void before_node(const osmium::Node& /*node*/) noexcept {
+        ++count_before;
+    }
+
+    void node_not_in_any_relation(const osmium::Node& /*node*/) noexcept {
+        ++count_not_in_any;
+    }
+
+    void after_node(const osmium::Node& /*node*/) noexcept {
+        ++count_after;
+    }
+
+    void before_way(const osmium::Way& /*way*/) noexcept {
+        ++count_before;
+    }
+
+    void way_not_in_any_relation(const osmium::Way& /*way*/) noexcept {
+        ++count_not_in_any;
+    }
+
+    void after_way(const osmium::Way& /*way*/) noexcept {
+        ++count_after;
+    }
+
+    void before_relation(const osmium::Relation& /*relation*/) noexcept {
+        ++count_before;
+    }
+
+    void relation_not_in_any_relation(const osmium::Relation& /*relation*/) noexcept {
+        ++count_not_in_any;
+    }
+
+    void after_relation(const osmium::Relation& /*relation*/) noexcept {
+        ++count_after;
+    }
+
+};
+
+struct CallbackRM : public osmium::relations::RelationsManager<CallbackRM, true, false, false> {
+
+    std::size_t count_nodes = 0;
+
+    bool new_relation(const osmium::Relation& /*relation*/) noexcept {
+        return true;
+    }
+
+    bool new_member(const osmium::Relation& /*relation*/, const osmium::RelationMember& member, std::size_t /*n*/) noexcept {
+        return member.type() == osmium::item_type::node;
+    }
+
+    void complete_relation(const osmium::Relation& relation) noexcept {
+        for (const auto& member : relation.members()) {
+            if (member.type() == osmium::item_type::node) {
+                ++count_nodes;
+                const auto* node = get_member_node(member.ref());
+                REQUIRE(node);
+                buffer().add_item(*node);
+                buffer().commit();
+            }
+        }
+    }
+
+};
+
+TEST_CASE("Use RelationsManager without any overloaded functions in derived class") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+
+    EmptyRM manager;
+
+    osmium::relations::read_relations(file, manager);
+
+    REQUIRE(manager.member_nodes_database().size()     == 2);
+    REQUIRE(manager.member_ways_database().size()      == 2);
+    REQUIRE(manager.member_relations_database().size() == 1);
+
+    REQUIRE(manager.member_database(osmium::item_type::node).size()     == 2);
+    REQUIRE(manager.member_database(osmium::item_type::way).size()      == 2);
+    REQUIRE(manager.member_database(osmium::item_type::relation).size() == 1);
+
+    const auto& m = manager;
+    REQUIRE(m.member_database(osmium::item_type::node).size()     == 2);
+    REQUIRE(m.member_database(osmium::item_type::way).size()      == 2);
+    REQUIRE(m.member_database(osmium::item_type::relation).size() == 1);
+
+    osmium::io::Reader reader{file};
+    osmium::apply(reader, manager.handler());
+    reader.close();
+}
+
+TEST_CASE("Relations manager derived class") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+
+    TestRM manager;
+
+    osmium::relations::read_relations(file, manager);
+
+    REQUIRE(manager.member_nodes_database().size()     == 2);
+    REQUIRE(manager.member_ways_database().size()      == 2);
+    REQUIRE(manager.member_relations_database().size() == 1);
+
+    bool callback_called = false;
+    osmium::io::Reader reader{file};
+    osmium::apply(reader, manager.handler([&](osmium::memory::Buffer&&) {
+        callback_called = true;
+    }));
+    reader.close();
+    REQUIRE_FALSE(callback_called);
+
+    REQUIRE(manager.count_new_rels      ==  3);
+    REQUIRE(manager.count_new_members   ==  5);
+    REQUIRE(manager.count_complete_rels ==  2);
+    REQUIRE(manager.count_before        == 10);
+    REQUIRE(manager.count_not_in_any    ==  6);
+    REQUIRE(manager.count_after         == 10);
+
+    int n = 0;
+    manager.for_each_incomplete_relation([&](const osmium::relations::RelationHandle& handle){
+        ++n;
+        REQUIRE(handle->id() == 31);
+        for (const auto& member : handle->members()) {
+            const auto* obj = manager.get_member_object(member);
+            if (member.ref() == 22) {
+                REQUIRE_FALSE(obj);
+            } else {
+                REQUIRE(obj);
+            }
+        }
+    });
+    REQUIRE(n == 1);
+}
+
+TEST_CASE("Relations manager with callback") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+
+    CallbackRM manager;
+
+    osmium::relations::read_relations(file, manager);
+
+    REQUIRE(manager.member_nodes_database().size()     == 2);
+    REQUIRE(manager.member_ways_database().size()      == 0);
+    REQUIRE(manager.member_relations_database().size() == 0);
+
+    bool callback_called = false;
+    osmium::io::Reader reader{file};
+    osmium::apply(reader, manager.handler([&](osmium::memory::Buffer&& buffer) {
+        callback_called = true;
+        REQUIRE(std::distance(buffer.begin(), buffer.end()) == 2);
+    }));
+    reader.close();
+    REQUIRE(manager.count_nodes == 2);
+    REQUIRE(callback_called);
+}
+
+TEST_CASE("Relations manager reading buffer without callback") {
+    osmium::io::File file{with_data_dir("t/relations/data.osm")};
+
+    CallbackRM manager;
+
+    osmium::relations::read_relations(file, manager);
+
+    REQUIRE(manager.member_nodes_database().size()     == 2);
+    REQUIRE(manager.member_ways_database().size()      == 0);
+    REQUIRE(manager.member_relations_database().size() == 0);
+
+    osmium::io::Reader reader{file};
+    osmium::apply(reader, manager.handler());
+    reader.close();
+
+    auto buffer = manager.read();
+    REQUIRE(std::distance(buffer.begin(), buffer.end()) == 2);
+
+    REQUIRE(manager.count_nodes == 2);
+}
+
+TEST_CASE("Access members via RelationsManager") {
+    EmptyRM manager;
+
+    manager.prepare_for_lookup();
+
+    REQUIRE(nullptr == manager.get_member_node(0));
+    REQUIRE(nullptr == manager.get_member_way(0));
+    REQUIRE(nullptr == manager.get_member_relation(0));
+
+    REQUIRE(nullptr == manager.get_member_node(17));
+    REQUIRE(nullptr == manager.get_member_way(17));
+    REQUIRE(nullptr == manager.get_member_relation(17));
+}
+
+TEST_CASE("Handle duplicate members correctly") {
+    osmium::io::File file{with_data_dir("t/relations/dupl_member.osm")};
+
+    TestRM manager;
+
+    osmium::relations::read_relations(file, manager);
+
+    auto c = manager.member_nodes_database().count();
+    REQUIRE(c.tracked   == 5);
+    REQUIRE(c.available == 0);
+    REQUIRE(c.removed   == 0);
+
+    osmium::io::Reader reader{file};
+    osmium::apply(reader, manager.handler());
+    reader.close();
+
+    c = manager.member_nodes_database().count();
+    REQUIRE(c.tracked   == 0);
+    REQUIRE(c.available == 0);
+    REQUIRE(c.removed   == 5);
+
+    REQUIRE(manager.count_new_rels      == 2);
+    REQUIRE(manager.count_new_members   == 5);
+    REQUIRE(manager.count_complete_rels == 2);
+    REQUIRE(manager.count_not_in_any    == 2); // 2 relations
+}
+
diff --git a/test/t/storage/test_item_stash.cpp b/test/t/storage/test_item_stash.cpp
new file mode 100644
index 0000000..62d910e
--- /dev/null
+++ b/test/t/storage/test_item_stash.cpp
@@ -0,0 +1,167 @@
+
+#include <catch.hpp>
+
+#include <sstream>
+
+#include <osmium/builder/attr.hpp>
+#include <osmium/storage/item_stash.hpp>
+
+osmium::memory::Buffer generate_test_data() {
+    using namespace osmium::builder::attr;
+
+    osmium::memory::Buffer buffer{1024 * 1024, osmium::memory::Buffer::auto_grow::yes};
+
+    const osmium::object_id_type num_nodes     = 100;
+    const osmium::object_id_type num_ways      =  50;
+    const osmium::object_id_type num_relations =  30;
+
+    osmium::object_id_type id = 1;
+    for (; id <= num_nodes; ++id) {
+        osmium::builder::add_node(buffer, _id(id));
+    }
+
+    for (; id <= num_nodes + num_ways; ++id) {
+        osmium::builder::add_way(buffer, _id(id));
+    }
+
+    for (; id <= num_nodes + num_ways + num_relations; ++id) {
+        osmium::builder::add_relation(buffer, _id(id));
+    }
+
+    return buffer;
+}
+
+
+TEST_CASE("Item stash handle") {
+    const auto handle = osmium::ItemStash::handle_type{};
+    REQUIRE_FALSE(handle.valid());
+
+    std::stringstream ss;
+    ss << handle;
+    REQUIRE(ss.str() == "-");
+}
+
+TEST_CASE("Item stash") {
+    const auto buffer = generate_test_data();
+
+    osmium::ItemStash stash;
+    REQUIRE(stash.size() == 0);
+    REQUIRE(stash.count_removed() == 0);
+
+    std::vector<osmium::ItemStash::handle_type> handles;
+    for (const auto& item : buffer) {
+        auto handle = stash.add_item(item);
+        handles.push_back(handle);
+    }
+
+    REQUIRE(stash.size() == 180);
+    REQUIRE(stash.count_removed() == 0);
+
+    REQUIRE(stash.used_memory() > 1024 * 1024);
+
+    osmium::object_id_type id = 1;
+    for (auto& handle : handles) { // must be reference because we will change it!
+        REQUIRE(handle.valid());
+        const auto& item = stash.get_item(handle);
+        bool correct_type = item.type() == osmium::item_type::node ||
+                            item.type() == osmium::item_type::way ||
+                            item.type() == osmium::item_type::relation;
+        REQUIRE(correct_type);
+        const auto& obj = static_cast<const osmium::OSMObject&>(item);
+        REQUIRE(obj.id() == id);
+
+        std::stringstream ss;
+        ss << handle;
+        REQUIRE(ss.str() == std::to_string(id));
+
+        if (obj.id() % 3 == 0) {
+            stash.remove_item(handle);
+            handle = osmium::ItemStash::handle_type{};
+        }
+
+        id++;
+    }
+
+    REQUIRE(stash.size() == 120);
+    REQUIRE(stash.count_removed() == 60);
+
+    id = 1;
+    int count_valid   = 0;
+    int count_invalid = 0;
+    for (auto handle : handles) {
+        if (handle.valid()) {
+            ++count_valid;
+            const auto& item = stash.get_item(handle);
+            const bool correct_type = item.type() == osmium::item_type::node ||
+                                      item.type() == osmium::item_type::way ||
+                                      item.type() == osmium::item_type::relation;
+            REQUIRE(correct_type);
+            const auto& obj = static_cast<const osmium::OSMObject&>(item);
+            REQUIRE(obj.id() == id);
+        } else {
+            ++count_invalid;
+        }
+        id++;
+    }
+
+    REQUIRE(count_valid   == 120);
+    REQUIRE(count_invalid ==  60);
+
+    stash.garbage_collect();
+    REQUIRE(stash.size() == 120);
+    REQUIRE(stash.count_removed() == 0);
+
+    id = 1;
+    for (auto handle : handles) {
+        if (handle.valid()) {
+            const auto& item = stash.get_item(handle);
+            const bool correct_type = item.type() == osmium::item_type::node ||
+                                      item.type() == osmium::item_type::way ||
+                                      item.type() == osmium::item_type::relation;
+            REQUIRE(correct_type);
+            const auto& obj = static_cast<const osmium::OSMObject&>(item);
+            REQUIRE(obj.id() == id);
+        }
+        id++;
+    }
+
+    stash.clear();
+    REQUIRE(stash.size() == 0);
+    REQUIRE(stash.count_removed() == 0);
+}
+
+TEST_CASE("Fill item stash until it garbage collects") {
+    const auto buffer = generate_test_data();
+
+    osmium::ItemStash stash;
+    REQUIRE(stash.size() == 0);
+    REQUIRE(stash.count_removed() == 0);
+
+    const auto& node = buffer.get<osmium::Node>(0);
+
+    std::vector<osmium::ItemStash::handle_type> handles;
+    std::size_t num_items = 6 * 1000 * 1000;
+    for (std::size_t i = 0; i < num_items; ++i) {
+        auto handle = stash.add_item(node);
+        handles.push_back(handle);
+    }
+
+    REQUIRE(stash.size() == num_items);
+    REQUIRE(stash.count_removed() == 0);
+
+    for (std::size_t i = 0; i < num_items; ++i) {
+        if (i % 10 != 0) {
+            stash.remove_item(handles[i]);
+        }
+    }
+
+    REQUIRE(stash.size() == num_items / 10);
+    REQUIRE(stash.count_removed() == num_items / 10 * 9);
+
+    // trigger compaction
+    stash.add_item(node);
+
+    REQUIRE(stash.size() == num_items / 10 + 1);
+    REQUIRE(stash.count_removed() == 0);
+}
+
diff --git a/test/t/tags/test_filter.cpp b/test/t/tags/test_filter.cpp
index eab8844..6c15a80 100644
--- a/test/t/tags/test_filter.cpp
+++ b/test/t/tags/test_filter.cpp
@@ -26,7 +26,7 @@ void check_filter(const osmium::TagList& tag_list,
 }
 
 const osmium::TagList& make_tag_list(osmium::memory::Buffer& buffer,
-                                     std::initializer_list<std::pair<const char*, const char*>> tags) {
+                                     const std::initializer_list<std::pair<const char*, const char*>>& tags) {
     const auto pos = osmium::builder::add_tag_list(buffer, osmium::builder::attr::_tags(tags));
     return buffer.get<osmium::TagList>(pos);
 }
@@ -131,9 +131,9 @@ TEST_CASE("KeyValueFilter") {
             { "source", "GPS" }
         });
 
-        REQUIRE( osmium::tags::match_any_of(tag_list, filter));
-        REQUIRE(!osmium::tags::match_all_of(tag_list, filter));
-        REQUIRE(!osmium::tags::match_none_of(tag_list, filter));
+        REQUIRE(     osmium::tags::match_any_of(tag_list, filter));
+        REQUIRE_FALSE(osmium::tags::match_all_of(tag_list, filter));
+        REQUIRE_FALSE(osmium::tags::match_none_of(tag_list, filter));
     }
 
     SECTION("KeyValueFilter matches against taglist with_all") {
@@ -147,9 +147,9 @@ TEST_CASE("KeyValueFilter") {
             { "name", "Main Street" }
         });
 
-        REQUIRE( osmium::tags::match_any_of(tag_list, filter));
-        REQUIRE( osmium::tags::match_all_of(tag_list, filter));
-        REQUIRE(!osmium::tags::match_none_of(tag_list, filter));
+        REQUIRE(      osmium::tags::match_any_of(tag_list, filter));
+        REQUIRE(      osmium::tags::match_all_of(tag_list, filter));
+        REQUIRE_FALSE(osmium::tags::match_none_of(tag_list, filter));
     }
 
     SECTION("KeyValueFilter matches against taglist with none") {
@@ -163,9 +163,9 @@ TEST_CASE("KeyValueFilter") {
             { "name", "Main Street" }
         });
 
-        REQUIRE(!osmium::tags::match_any_of(tag_list, filter));
-        REQUIRE(!osmium::tags::match_all_of(tag_list, filter));
-        REQUIRE( osmium::tags::match_none_of(tag_list, filter));
+        REQUIRE_FALSE(osmium::tags::match_any_of(tag_list, filter));
+        REQUIRE_FALSE(osmium::tags::match_all_of(tag_list, filter));
+        REQUIRE(      osmium::tags::match_none_of(tag_list, filter));
     }
 
     SECTION("KeyValueFilter matches against taglist with any called with rvalue") {
@@ -233,7 +233,7 @@ TEST_CASE("KeyPrefixFilter matches some keys") {
 
 }
 
-TEST_CASE("Generic Filterw with regex matches some keys") {
+TEST_CASE("Generic Filter with regex matches some keys") {
     osmium::memory::Buffer buffer{10240};
 
     osmium::tags::Filter<std::regex> filter{false};
diff --git a/test/t/tags/test_operators.cpp b/test/t/tags/test_operators.cpp
index 33a53c2..b90303a 100644
--- a/test/t/tags/test_operators.cpp
+++ b/test/t/tags/test_operators.cpp
@@ -6,56 +6,53 @@
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/tag.hpp>
 
-TEST_CASE("Operators") {
-
-    SECTION("Equal") {
-        osmium::memory::Buffer buffer1(10240);
-        {
-            osmium::builder::TagListBuilder tl_builder(buffer1);
-            tl_builder.add_tag("highway", "primary");
-            tl_builder.add_tag("name", "Main Street");
-            tl_builder.add_tag("source", "GPS");
-        }
-        buffer1.commit();
-
-        osmium::memory::Buffer buffer2(10240);
-        {
-            osmium::builder::TagListBuilder tl_builder(buffer2);
-            tl_builder.add_tag("highway", "primary");
-        }
-        buffer2.commit();
-
-        const osmium::TagList& tl1 = buffer1.get<const osmium::TagList>(0);
-        const osmium::TagList& tl2 = buffer2.get<const osmium::TagList>(0);
-
-        auto tagit1 = tl1.begin();
-        auto tagit2 = tl2.begin();
-        REQUIRE(*tagit1 == *tagit2);
-        ++tagit1;
-        REQUIRE(!(*tagit1 == *tagit2));
+TEST_CASE("Equality comparison of tags") {
+    osmium::memory::Buffer buffer1{10240};
+    {
+        osmium::builder::TagListBuilder tl_builder{buffer1};
+        tl_builder.add_tag("highway", "primary");
+        tl_builder.add_tag("name", "Main Street");
+        tl_builder.add_tag("source", "GPS");
     }
+    buffer1.commit();
 
-    SECTION("Order") {
-        osmium::memory::Buffer buffer(10240);
-        {
-            osmium::builder::TagListBuilder tl_builder(buffer);
-            tl_builder.add_tag("highway", "residential");
-            tl_builder.add_tag("highway", "primary");
-            tl_builder.add_tag("name", "Main Street");
-            tl_builder.add_tag("amenity", "post_box");
-        }
-        buffer.commit();
-
-        const osmium::TagList& tl = buffer.get<const osmium::TagList>(0);
-        const osmium::Tag& t1 = *(tl.begin());
-        const osmium::Tag& t2 = *(std::next(tl.begin(), 1));
-        const osmium::Tag& t3 = *(std::next(tl.begin(), 2));
-        const osmium::Tag& t4 = *(std::next(tl.begin(), 3));
-
-        REQUIRE(t2 < t1);
-        REQUIRE(t1 < t3);
-        REQUIRE(t2 < t3);
-        REQUIRE(t4 < t1);
+    osmium::memory::Buffer buffer2{10240};
+    {
+        osmium::builder::TagListBuilder tl_builder{buffer2};
+        tl_builder.add_tag("highway", "primary");
     }
+    buffer2.commit();
 
+    const osmium::TagList& tl1 = buffer1.get<const osmium::TagList>(0);
+    const osmium::TagList& tl2 = buffer2.get<const osmium::TagList>(0);
+
+    auto tagit1 = tl1.begin();
+    auto tagit2 = tl2.begin();
+    REQUIRE(*tagit1 == *tagit2);
+    ++tagit1;
+    REQUIRE_FALSE(*tagit1 == *tagit2);
 }
+
+TEST_CASE("Ordering of tags") {
+    osmium::memory::Buffer buffer{10240};
+    {
+        osmium::builder::TagListBuilder tl_builder{buffer};
+        tl_builder.add_tag("highway", "residential");
+        tl_builder.add_tag("highway", "primary");
+        tl_builder.add_tag("name", "Main Street");
+        tl_builder.add_tag("amenity", "post_box");
+    }
+    buffer.commit();
+
+    const osmium::TagList& tl = buffer.get<const osmium::TagList>(0);
+    const osmium::Tag& t1 = *(tl.begin());
+    const osmium::Tag& t2 = *(std::next(tl.begin(), 1));
+    const osmium::Tag& t3 = *(std::next(tl.begin(), 2));
+    const osmium::Tag& t4 = *(std::next(tl.begin(), 3));
+
+    REQUIRE(t2 < t1);
+    REQUIRE(t1 < t3);
+    REQUIRE(t2 < t3);
+    REQUIRE(t4 < t1);
+}
+
diff --git a/test/t/thread/test_pool.cpp b/test/t/thread/test_pool.cpp
index c1047db..5f0e5b6 100644
--- a/test/t/thread/test_pool.cpp
+++ b/test/t/thread/test_pool.cpp
@@ -15,7 +15,7 @@ struct test_job_with_result {
 
 struct test_job_throw {
     OSMIUM_NORETURN void operator()() const {
-        throw std::runtime_error("exception in pool thread");
+        throw std::runtime_error{"exception in pool thread"};
     }
 };
 
@@ -48,24 +48,70 @@ TEST_CASE("number of threads in pool") {
 
 }
 
+TEST_CASE("if zero number of threads requested, threads configured") {
+    osmium::thread::Pool pool{0};
+    REQUIRE(pool.num_threads() > 0);
+}
+
+TEST_CASE("if any negative number of threads requested, threads configured") {
+    osmium::thread::Pool pool{-1};
+    REQUIRE(pool.num_threads() > 0);
+}
+
+TEST_CASE("if outlier negative number of threads requested, threads configured") {
+    osmium::thread::Pool pool{-100};
+    REQUIRE(pool.num_threads() > 0);
+}
+
+TEST_CASE("if outlier positive number of threads requested, threads configured") {
+    osmium::thread::Pool pool{1000};
+    REQUIRE(pool.num_threads() > 0);
+}
+
 TEST_CASE("thread") {
 
-    auto& pool = osmium::thread::Pool::instance();
+    auto& pool = osmium::thread::Pool::default_instance();
+
+    SECTION("can get access to thread pool") {
+        REQUIRE(pool.queue_empty());
+    }
+
+    SECTION("can send job to thread pool") {
+        auto future = pool.submit(test_job_with_result{});
+
+        REQUIRE(future.get() == 42);
+    }
+
+    SECTION("can throw from job in thread pool") {
+        auto future = pool.submit(test_job_throw{});
+
+        REQUIRE_THROWS_AS(future.get(), const std::runtime_error&);
+    }
+
+}
+
+TEST_CASE("thread (user-provided pool)") {
+
+    osmium::thread::Pool pool{7};
 
     SECTION("can get access to thread pool") {
         REQUIRE(pool.queue_empty());
     }
+    
+    SECTION("can access user-provided number of threads") {
+        REQUIRE(pool.num_threads() == 7);
+    }
 
     SECTION("can send job to thread pool") {
-        auto future = pool.submit(test_job_with_result {});
+        auto future = pool.submit(test_job_with_result{});
 
         REQUIRE(future.get() == 42);
     }
 
     SECTION("can throw from job in thread pool") {
-        auto future = pool.submit(test_job_throw {});
+        auto future = pool.submit(test_job_throw{});
 
-        REQUIRE_THROWS_AS(future.get(), std::runtime_error);
+        REQUIRE_THROWS_AS(future.get(), const std::runtime_error&);
     }
 
 }
diff --git a/test/t/thread/test_util.cpp b/test/t/thread/test_util.cpp
new file mode 100644
index 0000000..419329e
--- /dev/null
+++ b/test/t/thread/test_util.cpp
@@ -0,0 +1,37 @@
+#include <catch.hpp>
+
+#include <stdexcept>
+
+#include <osmium/thread/util.hpp>
+
+TEST_CASE("check_for_exception") {
+    std::promise<int> p;
+    auto f = p.get_future();
+
+    SECTION("not ready") {
+        osmium::thread::check_for_exception(f);
+    }
+    SECTION("ready") {
+        p.set_value(3);
+        osmium::thread::check_for_exception(f);
+    }
+    SECTION("no shared state") {
+        p.set_value(3);
+        REQUIRE(f.get() == 3);
+        osmium::thread::check_for_exception(f);
+    }
+}
+
+TEST_CASE("check_for_exception with exception") {
+    std::promise<int> p;
+    auto f = p.get_future();
+
+    try {
+        throw std::runtime_error{"TEST"};
+    } catch(...) {
+        p.set_exception(std::current_exception());
+    }
+
+    REQUIRE_THROWS_AS(osmium::thread::check_for_exception(f), const std::runtime_error&);
+}
+
diff --git a/test/t/util/test_cast_with_assert.cpp b/test/t/util/test_cast_with_assert.cpp
index 2c65e0c..e9aaef7 100644
--- a/test/t/util/test_cast_with_assert.cpp
+++ b/test/t/util/test_cast_with_assert.cpp
@@ -6,7 +6,7 @@ struct assert_error : public std::runtime_error {
     explicit assert_error(const char* what_arg) : std::runtime_error(what_arg) {
     }
 };
-#define assert(x) if (!(x)) { throw(assert_error(#x)); }
+#define assert(x) if (!(x)) { throw assert_error{#x}; }
 
 #include <osmium/util/cast.hpp>
 
@@ -31,7 +31,7 @@ TEST_CASE("static_cast_with_assert: cast int32_t -> int16_t should not trigger a
 
 TEST_CASE("static_cast_with_assert: cast int32_t -> int16_t should trigger assert for large int") {
     const int32_t f = 100000;
-    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<int16_t>(f), assert_error);
+    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<int16_t>(f), const assert_error&);
 }
 
 
@@ -49,7 +49,7 @@ TEST_CASE("static_cast_with_assert: cast int16_t -> uint16_t should not trigger
 
 TEST_CASE("static_cast_with_assert: cast int16_t -> uint16_t should trigger assert for negative int") {
     const int16_t f = -1;
-    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<uint16_t>(f), assert_error);
+    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<uint16_t>(f), const assert_error&);
 }
 
 
@@ -67,7 +67,7 @@ TEST_CASE("static_cast_with_assert: cast uint32_t -> uint16_t should not trigger
 
 TEST_CASE("static_cast_with_assert: cast uint32_t -> uint16_t should trigger assert for large int") {
     const uint32_t f = 100000;
-    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<uint16_t>(f), assert_error);
+    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<uint16_t>(f), const assert_error&);
 }
 
 
@@ -79,6 +79,6 @@ TEST_CASE("static_cast_with_assert: cast uint16_t -> int16_t should not trigger
 
 TEST_CASE("static_cast_with_assert: cast uint16_t -> int16_t should trigger assert for large int") {
     const uint16_t f = 65000;
-    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<int16_t>(f), assert_error);
+    REQUIRE_THROWS_AS(osmium::static_cast_with_assert<int16_t>(f), const assert_error&);
 }
 
diff --git a/test/t/util/test_config.cpp b/test/t/util/test_config.cpp
new file mode 100644
index 0000000..1f168d1
--- /dev/null
+++ b/test/t/util/test_config.cpp
@@ -0,0 +1,75 @@
+#include "catch.hpp"
+
+#include <cstdlib>
+
+const char* env = nullptr;
+std::string name;
+
+const char* fake_getenv(const char* env_var) {
+    name = env_var;
+    return env;
+}
+
+#define getenv fake_getenv
+
+#include <osmium/util/config.hpp>
+
+TEST_CASE("get_pool_threads") {
+    env = nullptr;
+    REQUIRE(osmium::config::get_pool_threads() == 0);
+    REQUIRE(name == "OSMIUM_POOL_THREADS");
+    env = "";
+    REQUIRE(osmium::config::get_pool_threads() == 0);
+    env = "2";
+    REQUIRE(osmium::config::get_pool_threads() == 2);
+}
+
+TEST_CASE("use_pool_threads_for_pbf_parsing") {
+    env = nullptr;
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+    REQUIRE(name == "OSMIUM_USE_POOL_THREADS_FOR_PBF_PARSING");
+    env = "";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+
+    env = "off";
+    REQUIRE_FALSE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "OFF";
+    REQUIRE_FALSE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "false";
+    REQUIRE_FALSE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "no";
+    REQUIRE_FALSE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "No";
+    REQUIRE_FALSE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "0";
+    REQUIRE_FALSE(osmium::config::use_pool_threads_for_pbf_parsing());
+
+    env = "on";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "ON";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "true";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "yes";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "Yes";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+    env = "1";
+    REQUIRE(osmium::config::use_pool_threads_for_pbf_parsing());
+}
+
+TEST_CASE("get_max_queue_size") {
+    env = nullptr;
+    REQUIRE(osmium::config::get_max_queue_size("NAME", 0) == 0);
+    REQUIRE(name == "OSMIUM_MAX_NAME_QUEUE_SIZE");
+
+    REQUIRE(osmium::config::get_max_queue_size("NAME", 7) == 7);
+
+    env = "";
+    REQUIRE(osmium::config::get_max_queue_size("NAME", 7) == 7);
+    env = "0";
+    REQUIRE(osmium::config::get_max_queue_size("NAME", 7) == 7);
+    env = "3";
+    REQUIRE(osmium::config::get_max_queue_size("NAME", 7) == 3);
+}
+
diff --git a/test/t/util/test_delta.cpp b/test/t/util/test_delta.cpp
index 31b4276..f6f2a54 100644
--- a/test/t/util/test_delta.cpp
+++ b/test/t/util/test_delta.cpp
@@ -1,5 +1,6 @@
 #include "catch.hpp"
 
+#include <cstdint>
 #include <vector>
 
 #include <osmium/util/delta.hpp>
@@ -8,8 +9,26 @@ TEST_CASE("delta encode int") {
     osmium::util::DeltaEncode<int> x;
 
     REQUIRE(x.update(17) == 17);
+    REQUIRE(x.value() == 17);
     REQUIRE(x.update(10) == -7);
+    REQUIRE(x.value() == 10);
     REQUIRE(x.update(-10) == -20);
+    REQUIRE(x.value() == -10);
+    x.clear();
+    REQUIRE(x.value() == 0);
+}
+
+TEST_CASE("delta encode int with int32") {
+    osmium::util::DeltaEncode<int, int32_t> x;
+
+    REQUIRE(x.update(17) == 17);
+    REQUIRE(x.value() == 17);
+    REQUIRE(x.update(10) == -7);
+    REQUIRE(x.value() == 10);
+    REQUIRE(x.update(-10) == -20);
+    REQUIRE(x.value() == -10);
+    x.clear();
+    REQUIRE(x.value() == 0);
 }
 
 TEST_CASE("delta decode int") {
@@ -18,6 +37,18 @@ TEST_CASE("delta decode int") {
     REQUIRE(x.update(17) == 17);
     REQUIRE(x.update(10) == 27);
     REQUIRE(x.update(-40) == -13);
+    x.clear();
+    REQUIRE(x.update(17) == 17);
+}
+
+TEST_CASE("delta decode int with int32") {
+    osmium::util::DeltaDecode<int, int32_t> x;
+
+    REQUIRE(x.update(17) == 17);
+    REQUIRE(x.update(10) == 27);
+    REQUIRE(x.update(-40) == -13);
+    x.clear();
+    REQUIRE(x.update(17) == 17);
 }
 
 TEST_CASE("delta encode unsigned int") {
diff --git a/test/t/util/test_file.cpp b/test/t/util/test_file.cpp
index 475f285..17afd07 100644
--- a/test/t/util/test_file.cpp
+++ b/test/t/util/test_file.cpp
@@ -42,12 +42,12 @@ TEST_CASE("file_size") {
 #endif
 
     SECTION("illegal fd should throw") {
-        REQUIRE_THROWS_AS(osmium::util::file_size(-1), std::system_error);
+        REQUIRE_THROWS_AS(osmium::util::file_size(-1), const std::system_error&);
     }
 
     SECTION("unused fd should throw") {
         // its unlikely that fd 1000 is open...
-        REQUIRE_THROWS_AS(osmium::util::file_size(1000), std::system_error);
+        REQUIRE_THROWS_AS(osmium::util::file_size(1000), const std::system_error&);
     }
 
 }
@@ -59,12 +59,12 @@ TEST_CASE("resize_file") {
 #endif
 
     SECTION("illegal fd should throw") {
-        REQUIRE_THROWS_AS(osmium::util::resize_file(-1, 10), std::system_error);
+        REQUIRE_THROWS_AS(osmium::util::resize_file(-1, 10), const std::system_error&);
     }
 
     SECTION("unused fd should throw") {
         // its unlikely that fd 1000 is open...
-        REQUIRE_THROWS_AS(osmium::util::resize_file(1000, 10), std::system_error);
+        REQUIRE_THROWS_AS(osmium::util::resize_file(1000, 10), const std::system_error&);
     }
 
 }
diff --git a/test/t/util/test_options.cpp b/test/t/util/test_options.cpp
index 00bc1da..618fa89 100644
--- a/test/t/util/test_options.cpp
+++ b/test/t/util/test_options.cpp
@@ -2,62 +2,63 @@
 
 #include <osmium/util/options.hpp>
 
-TEST_CASE("Options") {
-
+TEST_CASE("Set a single option value from string") {
     osmium::util::Options o;
 
-    SECTION("set a single value from string") {
-        o.set("foo", "bar");
-        REQUIRE("bar" == o.get("foo"));
-        REQUIRE("" == o.get("empty"));
-        REQUIRE("default" == o.get("empty", "default"));
+    o.set("foo", "bar");
+    REQUIRE("bar" == o.get("foo"));
+    REQUIRE("" == o.get("empty"));
+    REQUIRE("default" == o.get("empty", "default"));
 
-        REQUIRE(!o.is_true("foo"));
-        REQUIRE(!o.is_true("empty"));
+    REQUIRE_FALSE(o.is_true("foo"));
+    REQUIRE_FALSE(o.is_true("empty"));
 
-        REQUIRE(o.is_not_false("foo"));
-        REQUIRE(o.is_not_false("empty"));
+    REQUIRE(o.is_not_false("foo"));
+    REQUIRE(o.is_not_false("empty"));
 
-        REQUIRE(1 == o.size());
-    }
+    REQUIRE(1 == o.size());
+}
 
-    SECTION("set values from booleans") {
-        o.set("t", true);
-        o.set("f", false);
-        REQUIRE("true" == o.get("t"));
-        REQUIRE("false" == o.get("f"));
-        REQUIRE("" == o.get("empty"));
+TEST_CASE("Set option values from booleans") {
+    osmium::util::Options o;
 
-        REQUIRE(o.is_true("t"));
-        REQUIRE(!o.is_true("f"));
+    o.set("t", true);
+    o.set("f", false);
+    REQUIRE("true" == o.get("t"));
+    REQUIRE("false" == o.get("f"));
+    REQUIRE("" == o.get("empty"));
 
-        REQUIRE(o.is_not_false("t"));
-        REQUIRE(!o.is_not_false("f"));
+    REQUIRE(o.is_true("t"));
+    REQUIRE_FALSE(o.is_true("f"));
 
-        REQUIRE(2 == o.size());
-    }
+    REQUIRE(o.is_not_false("t"));
+    REQUIRE_FALSE(o.is_not_false("f"));
 
-    SECTION("set value from string with equal sign") {
-        o.set("foo=bar");
-        REQUIRE("bar" == o.get("foo"));
-        REQUIRE(1 == o.size());
-    }
+    REQUIRE(2 == o.size());
+}
 
-    SECTION("set value from string without equal sign") {
-        o.set("foo");
-        REQUIRE("true" == o.get("foo"));
+TEST_CASE("Set option value from string with equal sign") {
+    osmium::util::Options o;
 
-        REQUIRE(o.is_true("foo"));
-        REQUIRE(o.is_not_false("foo"));
+    o.set("foo=bar");
+    REQUIRE("bar" == o.get("foo"));
+    REQUIRE(1 == o.size());
+}
 
-        REQUIRE(1 == o.size());
-    }
+TEST_CASE("Set option value from string without equal sign") {
+    osmium::util::Options o;
 
+    o.set("foo");
+    REQUIRE("true" == o.get("foo"));
+
+    REQUIRE(o.is_true("foo"));
+    REQUIRE(o.is_not_false("foo"));
+
+    REQUIRE(1 == o.size());
 }
 
 TEST_CASE("Options with initializer list") {
-
-    osmium::util::Options o{ { "foo", "true" }, { "bar", "17" } };
+    osmium::util::Options o{{ "foo", "true" }, { "bar", "17" }};
 
     REQUIRE(o.get("foo") == "true");
     REQUIRE(o.get("bar") == "17");
@@ -77,3 +78,41 @@ TEST_CASE("Options with initializer list") {
     }
 }
 
+TEST_CASE("Iterating over options") {
+    /*not const*/ osmium::util::Options o{{ "foo", "true" }, { "bar", "17" }};
+
+    auto it = o.begin();
+    REQUIRE(it->first == "bar");
+    REQUIRE(it->second == "17");
+    ++it;
+    REQUIRE(it->first == "foo");
+    REQUIRE(it->second == "true");
+    ++it;
+    REQUIRE(it == o.end());
+}
+
+TEST_CASE("Const iterating over options") {
+    const osmium::util::Options o{{ "foo", "true" }, { "bar", "17" }};
+
+    SECTION("begin/end") {
+        auto it = o.begin();
+        REQUIRE(it->first == "bar");
+        REQUIRE(it->second == "17");
+        ++it;
+        REQUIRE(it->first == "foo");
+        REQUIRE(it->second == "true");
+        ++it;
+        REQUIRE(it == o.end());
+    }
+    SECTION("cbegin/cend") {
+        auto it = o.cbegin();
+        REQUIRE(it->first == "bar");
+        REQUIRE(it->second == "17");
+        ++it;
+        REQUIRE(it->first == "foo");
+        REQUIRE(it->second == "true");
+        ++it;
+        REQUIRE(it == o.cend());
+    }
+}
+
diff --git a/test/t/util/test_string.cpp b/test/t/util/test_string.cpp
index e6b4e00..95e0e9a 100644
--- a/test/t/util/test_string.cpp
+++ b/test/t/util/test_string.cpp
@@ -3,62 +3,83 @@
 #include <osmium/util/string.hpp>
 
 TEST_CASE("split_string string") {
-    std::string str { "foo,baramba,baz" };
-    std::vector<std::string> result = {"foo", "baramba", "baz"};
+    const std::string str{"foo,baramba,baz"};
+    const std::vector<std::string> result = {"foo", "baramba", "baz"};
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(result == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ",;"));
+    REQUIRE(result == osmium::split_string(str, ",;", true));
 }
 
 TEST_CASE("split_string string without sep") {
-    std::string str { "foo" };
-    std::vector<std::string> result = {"foo"};
+    const std::string str{"foo"};
+    const std::vector<std::string> result = {"foo"};
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(result == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ",;"));
+    REQUIRE(result == osmium::split_string(str, ",;", true));
 }
 
 TEST_CASE("split_string string with empty at end") {
-    std::string str { "foo,bar," };
-    std::vector<std::string> result = {"foo", "bar", ""};
-    std::vector<std::string> resultc = {"foo", "bar"};
+    const std::string str{"foo,bar,"};
+    const std::vector<std::string> result = {"foo", "bar", ""};
+    const std::vector<std::string> resultc = {"foo", "bar"};
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(resultc == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ";,"));
+    REQUIRE(resultc == osmium::split_string(str, ";,", true));
 }
 
 TEST_CASE("split_string string with empty in middle") {
-    std::string str { "foo,,bar" };
-    std::vector<std::string> result = {"foo", "", "bar"};
-    std::vector<std::string> resultc = {"foo", "bar"};
+    const std::string str{"foo,,bar"};
+    const std::vector<std::string> result = {"foo", "", "bar"};
+    const std::vector<std::string> resultc = {"foo", "bar"};
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(resultc == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ",;"));
+    REQUIRE(resultc == osmium::split_string(str, ";,", true));
 }
 
 TEST_CASE("split_string string with empty at start") {
-    std::string str { ",bar,baz" };
-    std::vector<std::string> result = {"", "bar", "baz"};
-    std::vector<std::string> resultc = {"bar", "baz"};
+    const std::string str{",bar,baz"};
+    const std::vector<std::string> result = {"", "bar", "baz"};
+    const std::vector<std::string> resultc = {"bar", "baz"};
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(resultc == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ";,"));
+    REQUIRE(resultc == osmium::split_string(str, ",;", true));
 }
 
 TEST_CASE("split_string sep") {
-    std::string str { "," };
-    std::vector<std::string> result = {"", ""};
-    std::vector<std::string> resultc;
+    const std::string str{","};
+    const std::vector<std::string> result = {"", ""};
+    const std::vector<std::string> resultc;
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(resultc == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ",;"));
+    REQUIRE(resultc == osmium::split_string(str, ",;", true));
 }
 
 TEST_CASE("split_string empty string") {
-    std::string str { "" };
-    std::vector<std::string> result;
+    const std::string str{""};
+    const std::vector<std::string> result;
 
     REQUIRE(result == osmium::split_string(str, ','));
     REQUIRE(result == osmium::split_string(str, ',', true));
+    REQUIRE(result == osmium::split_string(str, ",;"));
+    REQUIRE(result == osmium::split_string(str, ",;", true));
+}
+
+TEST_CASE("split_string string with multiple sep characters") {
+    const std::string str{"foo,bar;baz-,blub"};
+    const std::vector<std::string> result = {"foo", "bar", "baz", "", "blub"};
+
+    REQUIRE(result == osmium::split_string(str, ";,-"));
 }
 
diff --git a/test/t/util/test_string_matcher.cpp b/test/t/util/test_string_matcher.cpp
index e9cd39e..64b2f28 100644
--- a/test/t/util/test_string_matcher.cpp
+++ b/test/t/util/test_string_matcher.cpp
@@ -35,7 +35,7 @@ TEST_CASE("String matcher: equal") {
     REQUIRE_FALSE(m.match("foobar"));
 }
 
-TEST_CASE("String matcher: prefix") {
+TEST_CASE("String matcher: prefix from const char*") {
     osmium::StringMatcher::prefix m{"foo"};
     REQUIRE(m.match("foo"));
     REQUIRE_FALSE(m.match("bar"));
@@ -43,7 +43,16 @@ TEST_CASE("String matcher: prefix") {
     REQUIRE_FALSE(m.match(""));
 }
 
-TEST_CASE("String matcher: substring") {
+TEST_CASE("String matcher: prefix from std::string") {
+    const std::string v{"foo"};
+    osmium::StringMatcher::prefix m{v};
+    REQUIRE(m.match("foo"));
+    REQUIRE_FALSE(m.match("bar"));
+    REQUIRE(m.match("foobar"));
+    REQUIRE_FALSE(m.match(""));
+}
+
+TEST_CASE("String matcher: substring from const char*") {
     osmium::StringMatcher::substring m{"foo"};
     REQUIRE(m.match("foo"));
     REQUIRE_FALSE(m.match("bar"));
@@ -52,6 +61,16 @@ TEST_CASE("String matcher: substring") {
     REQUIRE(m.match("xfoox"));
 }
 
+TEST_CASE("String matcher: substring from std::string") {
+    const std::string v{"foo"};
+    osmium::StringMatcher::substring m{v};
+    REQUIRE(m.match("foo"));
+    REQUIRE_FALSE(m.match("bar"));
+    REQUIRE(m.match("foobar"));
+    REQUIRE(m.match("barfoo"));
+    REQUIRE(m.match("xfoox"));
+}
+
 TEST_CASE("String matcher: empty prefix") {
     osmium::StringMatcher::prefix m{""};
     REQUIRE(m.match("foo"));
@@ -94,7 +113,7 @@ TEST_CASE("String matcher: list with add") {
     REQUIRE_FALSE(m.match("bar"));
     m.add_string("foo");
     REQUIRE(m.match("foo"));
-    m.add_string("bar");
+    m.add_string(std::string{"bar"});
     REQUIRE(m.match("bar"));
     REQUIRE_FALSE(m.match("foobar"));
     REQUIRE_FALSE(m.match("baz"));
@@ -146,11 +165,25 @@ TEST_CASE("Construct StringMatcher from list") {
 
 TEST_CASE("Construct StringMatcher") {
     osmium::StringMatcher m{osmium::StringMatcher::equal{"foo"}};
+    REQUIRE(print(m) == "equal[foo]");
     REQUIRE(m("foo"));
     REQUIRE_FALSE(m("bar"));
 
     m = osmium::StringMatcher::list{{"foo", "bar"}};
     REQUIRE(m("foo"));
-    REQUIRE(m("bar"));
+    REQUIRE(m(std::string{"bar"}));
+    REQUIRE(print(m) == "list[[foo][bar]]");
+
+    m = osmium::StringMatcher::prefix{"foo"};
+    REQUIRE(m("foo"));
+    REQUIRE(m("foobar"));
+    REQUIRE_FALSE(m("barfoo"));
+    REQUIRE(print(m) == "prefix[foo]");
+
+    m = osmium::StringMatcher::substring{"foo"};
+    REQUIRE(m("foo"));
+    REQUIRE(m("foobar"));
+    REQUIRE(m(std::string{"barfoo"}));
+    REQUIRE(print(m) == "substring[foo]");
 }
 
diff --git a/test/t/util/test_timer_disabled.cpp b/test/t/util/test_timer_disabled.cpp
new file mode 100644
index 0000000..2bf01ff
--- /dev/null
+++ b/test/t/util/test_timer_disabled.cpp
@@ -0,0 +1,15 @@
+#include "catch.hpp"
+
+#include <chrono>
+#include <thread>
+
+#include <osmium/util/timer.hpp>
+
+TEST_CASE("timer") {
+    osmium::Timer timer;
+    timer.start();
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    timer.stop();
+    REQUIRE(timer.elapsed_microseconds() == 0);
+}
+
diff --git a/test/t/util/test_timer_enabled.cpp b/test/t/util/test_timer_enabled.cpp
new file mode 100644
index 0000000..c1fb665
--- /dev/null
+++ b/test/t/util/test_timer_enabled.cpp
@@ -0,0 +1,16 @@
+#include "catch.hpp"
+
+#include <chrono>
+#include <thread>
+
+#define OSMIUM_WITH_TIMER
+#include <osmium/util/timer.hpp>
+
+TEST_CASE("timer") {
+    osmium::Timer timer;
+    timer.start();
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    timer.stop();
+    REQUIRE(timer.elapsed_microseconds() > 900);
+}
+

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



More information about the Pkg-grass-devel mailing list