diff --git a/core/src/eigen_types.hh b/core/src/eigen_types.hh
index 0d1410e65e46132113168b458fec53fe94bc33d9..89c90f06a002ddccf925389b887ecca0804b9879 100644
--- a/core/src/eigen_types.hh
+++ b/core/src/eigen_types.hh
@@ -33,9 +33,11 @@ typedef Eigen::Matrix<double,16,16> EMat16;
 
 typedef Eigen::Matrix<Real,3,1> EVec3;
 typedef Eigen::Matrix<Real,4,1> EVec4;
+typedef Eigen::Matrix<Real,Eigen::Dynamic,1> EVecX;
 
 typedef Eigen::Matrix<Real,1,3> ERVec3;
 typedef Eigen::Matrix<Real,1,4> ERVec4;
+typedef Eigen::Matrix<Real,1,Eigen::Dynamic> ERVecX;
 
 // some special matrices used at various locations
 typedef Eigen::Matrix<Real,Eigen::Dynamic,3> EMatX3;
diff --git a/modelling/pymod/export_pocket_finder.cc b/modelling/pymod/export_pocket_finder.cc
index b2e5e683fe8358f1bbb71c37efd5396cd8d83c3a..1f9482deb5c78bad4ebe4cd904279776e781e29c 100644
--- a/modelling/pymod/export_pocket_finder.cc
+++ b/modelling/pymod/export_pocket_finder.cc
@@ -27,6 +27,7 @@ using namespace boost::python;
 
 boost::shared_ptr<PocketQuery> WrapInitPositions(const geom::Vec3List& positions, 
                                                  const String& identifier) {
+  
   boost::shared_ptr<PocketQuery> ptr(new PocketQuery(positions, identifier));
   return ptr;
 }
@@ -43,16 +44,56 @@ boost::shared_ptr<PocketQuery> WrapInitQueries(const list& queries) {
 }
 
 
+boost::python::list WrapFindPockets(const PocketQuery& query,
+                                    const geom::Vec3List& positions,
+                                    Real coverage_thresh,
+                                    Real distance_thresh) {
+
+  std::vector<PocketMatch> v_result = FindPockets(query, positions, 
+                                                  coverage_thresh,
+                                                  distance_thresh);
+  list return_list;
+  for(std::vector<PocketMatch>::iterator it = v_result.begin();
+      it != v_result.end(); ++it) {
+    return_list.append(*it);
+  }
+  return return_list;
+}
+
+
+boost::python::list WrapGetAlignment(const PocketMatch& match) {
+
+  std::vector<std::pair<int,int> > aln = match.aln;
+  list return_list;
+  for(std::vector<std::pair<int,int> >::iterator it = aln.begin();
+      it != aln.end(); ++it) {
+    return_list.append(boost::python::make_tuple(it->first, it->second));
+  }
+  return return_list;
+}
+
+
 void export_pocket_finder() {
 
   class_<PocketQuery>("PocketQuery", no_init)
     .def("__init__", make_constructor(&WrapInitPositions))
     .def("__init__", make_constructor(&WrapInitQueries))
-    .def("GetPositions", &PocketQuery::GetPositions, return_value_policy<copy_const_reference>(), (arg("query_idx")))
-    .def("GetIdentifiers", &PocketQuery::GetIdentifiers, return_value_policy<copy_const_reference>())
+    .def("GetPositions", &PocketQuery::GetPositions, 
+         return_value_policy<copy_const_reference>(), (arg("query_idx")))
+    .def("GetIdentifiers", &PocketQuery::GetIdentifiers, 
+         return_value_policy<copy_const_reference>())
     .def("GetN", &PocketQuery::GetN)
+    .def("Save", &PocketQuery::Save, (arg("filename")))
+    .def("Load", &PocketQuery::Load, (arg("filename"))).staticmethod("Load")
   ;
 
+  class_<PocketMatch>("PocketMatch", no_init)
+    .def_readonly("query_idx", &PocketMatch::query_idx)
+    .def_readonly("mat", &PocketMatch::mat)
+    .add_property("alignment", &WrapGetAlignment)
+  ;
 
-  def("PocketFinder", &promod3::modelling::PocketFinder, (arg("query"), arg("target_positions")));
+  def("FindPockets", &WrapFindPockets, (arg("query"), arg("target_positions"), 
+                                        arg("coverage_thresh")=0.5,
+                                        arg("distance_thresh")=1.0));
 }
diff --git a/modelling/src/CMakeLists.txt b/modelling/src/CMakeLists.txt
index 6b874482860120b32aedd7555c1c01c99e0a89ed..dffc797cca3fdd98d0ad8b0cba8f9678f14a1408 100644
--- a/modelling/src/CMakeLists.txt
+++ b/modelling/src/CMakeLists.txt
@@ -42,9 +42,7 @@ set(MODELLING_HEADERS
   sidechain_reconstructor.hh
   sidechain_env_listener.hh
   pocket_finder.hh
-  hopscotch_map.h
-  hopscotch_hash.h
-  hopscotch_growth_policy.h
+  robin_hood.h
 )
 
 module(NAME modelling
diff --git a/modelling/src/hopscotch_growth_policy.h b/modelling/src/hopscotch_growth_policy.h
deleted file mode 100644
index 08b2ffd926e94c185f8c8cfefe557d6fe8df481f..0000000000000000000000000000000000000000
--- a/modelling/src/hopscotch_growth_policy.h
+++ /dev/null
@@ -1,301 +0,0 @@
-//////////////////////////////////////////////////////
-// SOURCE: https://github.com/Tessil/hopscotch-map  //
-// COMMIT: 4d0cccd3b41294f843925e495117919a53aa61a0 //
-// WE ASSUME THAT NO UPDATES ARE REQUIRED           //
-//////////////////////////////////////////////////////
-
-/**
- * MIT License
- * 
- * Copyright (c) 2018 Tessil
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- * 
- * 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef TSL_HOPSCOTCH_GROWTH_POLICY_H
-#define TSL_HOPSCOTCH_GROWTH_POLICY_H 
-
-
-#include <algorithm>
-#include <array>
-#include <climits>
-#include <cmath>
-#include <cstddef>
-#include <iterator>
-#include <limits>
-#include <ratio>
-#include <stdexcept>
-
-
-namespace tsl {
-namespace hh {
-
-/**
- * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a power of two. It allows
- * the table to use a mask operation instead of a modulo operation to map a hash to a bucket.
- * 
- * GrowthFactor must be a power of two >= 2.
- */
-template<std::size_t GrowthFactor>
-class power_of_two_growth_policy {
-public:
-    /**
-     * Called on the hash table creation and on rehash. The number of buckets for the table is passed in parameter.
-     * This number is a minimum, the policy may update this value with a higher value if needed (but not lower).
-     *
-     * If 0 is given, min_bucket_count_in_out must still be 0 after the policy creation and
-     * bucket_for_hash must always return 0 in this case.
-     */
-    explicit power_of_two_growth_policy(std::size_t& min_bucket_count_in_out) {
-        if(min_bucket_count_in_out > max_bucket_count()) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        if(min_bucket_count_in_out > 0) {
-            min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out);
-            m_mask = min_bucket_count_in_out - 1;
-        }
-        else {
-            m_mask = 0;
-        }
-    }
-    
-    /**
-     * Return the bucket [0, bucket_count()) to which the hash belongs. 
-     * If bucket_count() is 0, it must always return 0.
-     */
-    std::size_t bucket_for_hash(std::size_t hash) const noexcept {
-        return hash & m_mask;
-    }
-    
-    /**
-     * Return the bucket count to use when the bucket array grows on rehash.
-     */
-    std::size_t next_bucket_count() const {
-        if((m_mask + 1) > max_bucket_count() / GrowthFactor) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        return (m_mask + 1) * GrowthFactor;
-    }
-    
-    /**
-     * Return the maximum number of buckets supported by the policy.
-     */
-    std::size_t max_bucket_count() const {
-        // Largest power of two.
-        return (std::numeric_limits<std::size_t>::max() / 2) + 1;
-    }
-    
-    /**
-     * Reset the growth policy as if it was created with a bucket count of 0.
-     * After a clear, the policy must always return 0 when bucket_for_hash is called.
-     */
-    void clear() noexcept {
-        m_mask = 0;
-    }
-    
-private:
-    static std::size_t round_up_to_power_of_two(std::size_t value) {
-        if(is_power_of_two(value)) {
-            return value;
-        }
-        
-        if(value == 0) {
-            return 1;
-        }
-            
-        --value;
-        for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) {
-            value |= value >> i;
-        }
-        
-        return value + 1;
-    }
-    
-    static constexpr bool is_power_of_two(std::size_t value) {
-        return value != 0 && (value & (value - 1)) == 0;
-    }
-    
-private:
-    static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, "GrowthFactor must be a power of two >= 2.");
-    
-    std::size_t m_mask;
-};
-
-
-/**
- * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo to map a hash
- * to a bucket. Slower but it can be useful if you want a slower growth.
- */
-template<class GrowthFactor = std::ratio<3, 2>>
-class mod_growth_policy {
-public:
-    explicit mod_growth_policy(std::size_t& min_bucket_count_in_out) {
-        if(min_bucket_count_in_out > max_bucket_count()) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        if(min_bucket_count_in_out > 0) {
-            m_mod = min_bucket_count_in_out;
-        }
-        else {
-            m_mod = 1;
-        }
-    }
-    
-    std::size_t bucket_for_hash(std::size_t hash) const noexcept {
-        return hash % m_mod;
-    }
-    
-    std::size_t next_bucket_count() const {
-        if(m_mod == max_bucket_count()) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        const double next_bucket_count = std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR);
-        if(!std::isnormal(next_bucket_count)) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        if(next_bucket_count > double(max_bucket_count())) {
-            return max_bucket_count();
-        }
-        else {
-            return std::size_t(next_bucket_count);
-        }
-    }
-    
-    std::size_t max_bucket_count() const {
-        return MAX_BUCKET_COUNT;
-    }
-    
-    void clear() noexcept {
-        m_mod = 1;
-    }
-    
-private:
-    static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den;
-    static const std::size_t MAX_BUCKET_COUNT = 
-            std::size_t(double(
-                    std::numeric_limits<std::size_t>::max() / REHASH_SIZE_MULTIPLICATION_FACTOR
-            ));
-            
-    static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1.");
-    
-    std::size_t m_mod;
-};
-
-
-
-namespace detail {
-
-static constexpr const std::array<std::size_t, 40> PRIMES = {{
-    1ul, 5ul, 17ul, 29ul, 37ul, 53ul, 67ul, 79ul, 97ul, 131ul, 193ul, 257ul, 389ul, 521ul, 769ul, 1031ul, 
-    1543ul, 2053ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 
-    1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 
-    402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul
-}};
-
-template<unsigned int IPrime>
-static constexpr std::size_t mod(std::size_t hash) { return hash % PRIMES[IPrime]; }
-
-// MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the
-// compiler can optimize the modulo code better with a constant known at the compilation.
-static constexpr const std::array<std::size_t(*)(std::size_t), 40> MOD_PRIME = {{ 
-    &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>, 
-    &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, 
-    &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>, 
-    &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>, &mod<39>
-}};
-
-}
-
-/**
- * Grow the hash table by using prime numbers as bucket count. Slower than tsl::hh::power_of_two_growth_policy in  
- * general but will probably distribute the values around better in the buckets with a poor hash function.
- * 
- * To allow the compiler to optimize the modulo operation, a lookup table is used with constant primes numbers.
- * 
- * With a switch the code would look like:
- * \code
- * switch(iprime) { // iprime is the current prime of the hash table
- *     case 0: hash % 5ul;
- *             break;
- *     case 1: hash % 17ul;
- *             break;
- *     case 2: hash % 29ul;
- *             break;
- *     ...
- * }    
- * \endcode
- * 
- * Due to the constant variable in the modulo the compiler is able to optimize the operation
- * by a series of multiplications, substractions and shifts. 
- * 
- * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * 5' in a 64 bits environement.
- */
-class prime_growth_policy {
-public:
-    explicit prime_growth_policy(std::size_t& min_bucket_count_in_out) {
-        auto it_prime = std::lower_bound(detail::PRIMES.begin(), 
-                                         detail::PRIMES.end(), min_bucket_count_in_out);
-        if(it_prime == detail::PRIMES.end()) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        m_iprime = static_cast<unsigned int>(std::distance(detail::PRIMES.begin(), it_prime));
-        if(min_bucket_count_in_out > 0) {
-            min_bucket_count_in_out = *it_prime;
-        }
-        else {
-            min_bucket_count_in_out = 0;
-        }
-    }
-    
-    std::size_t bucket_for_hash(std::size_t hash) const noexcept {
-        return detail::MOD_PRIME[m_iprime](hash);
-    }
-    
-    std::size_t next_bucket_count() const {
-        if(m_iprime + 1 >= detail::PRIMES.size()) {
-            throw std::length_error("The hash table exceeds its maxmimum size.");
-        }
-        
-        return detail::PRIMES[m_iprime + 1];
-    }   
-    
-    std::size_t max_bucket_count() const {
-        return detail::PRIMES.back();
-    }
-    
-    void clear() noexcept {
-        m_iprime = 0;
-    }
-    
-private:
-    unsigned int m_iprime;
-    
-    static_assert(std::numeric_limits<decltype(m_iprime)>::max() >= detail::PRIMES.size(), 
-                  "The type of m_iprime is not big enough.");
-}; 
-
-}
-}
-
-#endif
diff --git a/modelling/src/hopscotch_hash.h b/modelling/src/hopscotch_hash.h
deleted file mode 100644
index 30fb635f594b8a9f4ace81181b0d9767a2366480..0000000000000000000000000000000000000000
--- a/modelling/src/hopscotch_hash.h
+++ /dev/null
@@ -1,1828 +0,0 @@
-//////////////////////////////////////////////////////
-// SOURCE: https://github.com/Tessil/hopscotch-map  //
-// COMMIT: 4d0cccd3b41294f843925e495117919a53aa61a0 //
-// WE ASSUME THAT NO UPDATES ARE REQUIRED           //
-//////////////////////////////////////////////////////
-
-/**
- * MIT License
- * 
- * Copyright (c) 2017 Tessil
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- * 
- * 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef TSL_HOPSCOTCH_HASH_H
-#define TSL_HOPSCOTCH_HASH_H
-
-
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <cstddef>
-#include <cstdint>
-#include <exception>
-#include <functional>
-#include <initializer_list>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <stdexcept>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-#include <vector>
-#include "hopscotch_growth_policy.h"
-
-
-
-#if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9))
-#    define TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR
-#endif
-
-
-/*
- * Only activate tsl_hh_assert if TSL_DEBUG is defined. 
- * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_hh_assert is used a lot
- * (people usually compile with "-O3" and not "-O3 -DNDEBUG").
- */
-#ifdef TSL_DEBUG
-#    define tsl_hh_assert(expr) assert(expr)
-#else
-#    define tsl_hh_assert(expr) (static_cast<void>(0))
-#endif
-
-
-namespace tsl {
-
-namespace detail_hopscotch_hash {
-    
-    
-template<typename T>
-struct make_void {
-    using type = void;
-};
-
-
-template<typename T, typename = void>
-struct has_is_transparent : std::false_type {
-};
-
-template<typename T>
-struct has_is_transparent<T, typename make_void<typename T::is_transparent>::type> : std::true_type {
-};
-
-
-template<typename T, typename = void>
-struct has_key_compare : std::false_type {
-};
-
-template<typename T>
-struct has_key_compare<T, typename make_void<typename T::key_compare>::type> : std::true_type {
-};
-
-
-template<typename U>
-struct is_power_of_two_policy: std::false_type {
-};
-
-template<std::size_t GrowthFactor>
-struct is_power_of_two_policy<tsl::hh::power_of_two_growth_policy<GrowthFactor>>: std::true_type {
-};
-
-
-
-
-
-/*
- * smallest_type_for_min_bits::type returns the smallest type that can fit MinBits.
- */
-static const std::size_t SMALLEST_TYPE_MAX_BITS_SUPPORTED = 64;
-template<unsigned int MinBits, typename Enable = void>
-class smallest_type_for_min_bits {
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 0) && (MinBits <= 8)>::type> {
-public:
-    using type = std::uint_least8_t;
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 8) && (MinBits <= 16)>::type> {
-public:
-    using type = std::uint_least16_t;
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 16) && (MinBits <= 32)>::type> {
-public:
-    using type = std::uint_least32_t;
-};
-
-template<unsigned int MinBits>
-class smallest_type_for_min_bits<MinBits, typename std::enable_if<(MinBits > 32) && (MinBits <= 64)>::type> {
-public:
-    using type = std::uint_least64_t;
-};
-        
-
-
-/*
- * Each bucket may store up to three elements:
- * - An aligned storage to store a value_type object with placement-new.
- * - An (optional) hash of the value in the bucket.
- * - An unsigned integer of type neighborhood_bitmap used to tell us which buckets in the neighborhood of the 
- *   current bucket contain a value with a hash belonging to the current bucket. 
- * 
- * For a bucket 'bct', a bit 'i' (counting from 0 and from the least significant bit to the most significant)
- * set to 1 means that the bucket 'bct + i' contains a value with a hash belonging to bucket 'bct'.
- * The bits used for that, start from the third least significant bit.
- * The two least significant bits are reserved:
- * - The least significant bit is set to 1 if there is a value in the bucket storage.
- * - The second least significant bit is set to 1 if there is an overflow. More than NeighborhoodSize values
-  * give the same hash, all overflow values are stored in the m_overflow_elements list of the map.
- *
- * Details regarding hopscotch hashing an its implementation can be found here:
- *  https://tessil.github.io/2016/08/29/hopscotch-hashing.html
- */
-static const std::size_t NB_RESERVED_BITS_IN_NEIGHBORHOOD = 2; 
-
-
-using truncated_hash_type = std::uint_least32_t;
-
-/**
- * Helper class that stores a truncated hash if StoreHash is true and nothing otherwise.
- */
-template<bool StoreHash>
-class hopscotch_bucket_hash {
-public:
-    bool bucket_hash_equal(std::size_t /*hash*/) const noexcept {
-        return true;
-    }
-    
-    truncated_hash_type truncated_bucket_hash() const noexcept {
-        return 0;
-    }
-    
-protected:    
-    void copy_hash(const hopscotch_bucket_hash& ) noexcept {
-    }
-    
-    void set_hash(truncated_hash_type /*hash*/) noexcept {
-    }
-};
-
-template<>
-class hopscotch_bucket_hash<true> {
-public:
-    bool bucket_hash_equal(std::size_t hash) const noexcept {
-        return m_hash == truncated_hash_type(hash);
-    }
-    
-    truncated_hash_type truncated_bucket_hash() const noexcept {
-        return m_hash;
-    }
-    
-protected:    
-    void copy_hash(const hopscotch_bucket_hash& bucket) noexcept {
-        m_hash = bucket.m_hash;
-    }
-    
-    void set_hash(truncated_hash_type hash) noexcept {
-        m_hash = hash;
-    }
-    
-private:    
-    truncated_hash_type m_hash;
-};
-
-
-template<typename ValueType, unsigned int NeighborhoodSize, bool StoreHash>
-class hopscotch_bucket: public hopscotch_bucket_hash<StoreHash> {
-private:
-    static const std::size_t MIN_NEIGHBORHOOD_SIZE = 4;
-    static const std::size_t MAX_NEIGHBORHOOD_SIZE = SMALLEST_TYPE_MAX_BITS_SUPPORTED - NB_RESERVED_BITS_IN_NEIGHBORHOOD; 
-    
-    
-    static_assert(NeighborhoodSize >= 4, "NeighborhoodSize should be >= 4.");
-    // We can't put a variable in the message, ensure coherence
-    static_assert(MIN_NEIGHBORHOOD_SIZE == 4, ""); 
-    
-    static_assert(NeighborhoodSize <= 62, "NeighborhoodSize should be <= 62.");
-    // We can't put a variable in the message, ensure coherence
-    static_assert(MAX_NEIGHBORHOOD_SIZE == 62, ""); 
-    
-    
-    static_assert(!StoreHash || NeighborhoodSize <= 30, 
-                  "NeighborhoodSize should be <= 30 if StoreHash is true.");
-    // We can't put a variable in the message, ensure coherence
-    static_assert(MAX_NEIGHBORHOOD_SIZE - 32 == 30, "");
-    
-    using bucket_hash = hopscotch_bucket_hash<StoreHash>;
-    
-public:
-    using value_type = ValueType;
-    using neighborhood_bitmap = 
-                typename smallest_type_for_min_bits<NeighborhoodSize + NB_RESERVED_BITS_IN_NEIGHBORHOOD>::type;
-
-
-    hopscotch_bucket() noexcept: bucket_hash(), m_neighborhood_infos(0) {
-        tsl_hh_assert(empty());
-    }
-    
-    
-    hopscotch_bucket(const hopscotch_bucket& bucket) 
-        noexcept(std::is_nothrow_copy_constructible<value_type>::value): bucket_hash(bucket), 
-                                                                         m_neighborhood_infos(0) 
-    {
-        if(!bucket.empty()) {
-            ::new (static_cast<void*>(std::addressof(m_value))) value_type(bucket.value());
-        }
-        
-        m_neighborhood_infos = bucket.m_neighborhood_infos;
-    }
-    
-    hopscotch_bucket(hopscotch_bucket&& bucket)
-        noexcept(std::is_nothrow_move_constructible<value_type>::value) : bucket_hash(std::move(bucket)),
-                                                                          m_neighborhood_infos(0) 
-    {
-        if(!bucket.empty()) {
-            ::new (static_cast<void*>(std::addressof(m_value))) value_type(std::move(bucket.value()));
-        }
-        
-        m_neighborhood_infos = bucket.m_neighborhood_infos;
-    }
-     
-    hopscotch_bucket& operator=(const hopscotch_bucket& bucket) 
-        noexcept(std::is_nothrow_copy_constructible<value_type>::value) 
-    {
-        if(this != &bucket) {
-            remove_value();
-            
-            bucket_hash::operator=(bucket);
-            if(!bucket.empty()) {
-                ::new (static_cast<void*>(std::addressof(m_value))) value_type(bucket.value());
-            }
-            
-            m_neighborhood_infos = bucket.m_neighborhood_infos;
-        }
-        
-        return *this;
-    }
-    
-    hopscotch_bucket& operator=(hopscotch_bucket&& ) = delete;
-     
-    ~hopscotch_bucket() noexcept {
-        if(!empty()) {
-            destroy_value();
-        }
-    }
-    
-    neighborhood_bitmap neighborhood_infos() const noexcept {
-        return neighborhood_bitmap(m_neighborhood_infos >> NB_RESERVED_BITS_IN_NEIGHBORHOOD);
-    }
-    
-    void set_overflow(bool has_overflow) noexcept {
-        if(has_overflow) {
-            m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 2);
-        }
-        else {
-            m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~2);
-        }
-    }
-    
-    bool has_overflow() const noexcept {
-        return (m_neighborhood_infos & 2) != 0;
-    }
-    
-    bool empty() const noexcept {
-        return (m_neighborhood_infos & 1) == 0;
-    }
-    
-    void toggle_neighbor_presence(std::size_t ineighbor) noexcept {
-        tsl_hh_assert(ineighbor <= NeighborhoodSize);
-        m_neighborhood_infos = neighborhood_bitmap(
-                                    m_neighborhood_infos ^ (1ull << (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)));
-    }
-    
-    bool check_neighbor_presence(std::size_t ineighbor) const noexcept {
-        tsl_hh_assert(ineighbor <= NeighborhoodSize);
-        if(((m_neighborhood_infos >> (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)) & 1) == 1) {
-            return true;
-        }
-        
-        return false;
-    }
-    
-    value_type& value() noexcept {
-        tsl_hh_assert(!empty());
-        return *reinterpret_cast<value_type*>(std::addressof(m_value));
-    }
-    
-    const value_type& value() const noexcept {
-        tsl_hh_assert(!empty());
-        return *reinterpret_cast<const value_type*>(std::addressof(m_value));
-    }
-    
-    template<typename... Args>
-    void set_value_of_empty_bucket(truncated_hash_type hash, Args&&... value_type_args) {
-        tsl_hh_assert(empty());
-        
-        ::new (static_cast<void*>(std::addressof(m_value))) value_type(std::forward<Args>(value_type_args)...);
-        set_empty(false);
-        this->set_hash(hash);
-    }
-    
-    void swap_value_into_empty_bucket(hopscotch_bucket& empty_bucket) {
-        tsl_hh_assert(empty_bucket.empty());
-        if(!empty()) {
-            ::new (static_cast<void*>(std::addressof(empty_bucket.m_value))) value_type(std::move(value()));
-            empty_bucket.copy_hash(*this);
-            empty_bucket.set_empty(false);
-            
-            destroy_value();
-            set_empty(true);
-        }
-    }
-    
-    void remove_value() noexcept {
-        if(!empty()) {
-            destroy_value();
-            set_empty(true);
-        }
-    }
-    
-    void clear() noexcept {
-        if(!empty()) {
-            destroy_value();
-        }
-        
-        m_neighborhood_infos = 0;
-        tsl_hh_assert(empty());
-    }
-    
-    static truncated_hash_type truncate_hash(std::size_t hash) noexcept {
-        return truncated_hash_type(hash);
-    }
-    
-private:
-    void set_empty(bool is_empty) noexcept {
-        if(is_empty) {
-            m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~1);
-        }
-        else {
-            m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 1);
-        }
-    }
-    
-    void destroy_value() noexcept {
-        tsl_hh_assert(!empty());
-        value().~value_type();
-    }
-    
-private:
-    using storage = typename std::aligned_storage<sizeof(value_type), alignof(value_type)>::type;
-    
-    neighborhood_bitmap m_neighborhood_infos;
-    storage m_value;
-};
-
-
-/**
- * Internal common class used by (b)hopscotch_map and (b)hopscotch_set.
- * 
- * ValueType is what will be stored by hopscotch_hash (usually std::pair<Key, T> for a map and Key for a set).
- * 
- * KeySelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the key.
- * 
- * ValueSelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the value.
- * ValueSelect should be void if there is no value (in a set for example).
- * 
- * OverflowContainer will be used as containers for overflown elements. Usually it should be a list<ValueType>
- * or a set<Key>/map<Key, T>.
- */
-template<class ValueType,
-         class KeySelect,
-         class ValueSelect,
-         class Hash,
-         class KeyEqual,
-         class Allocator,
-         unsigned int NeighborhoodSize,
-         bool StoreHash,
-         class GrowthPolicy,
-         class OverflowContainer>
-class hopscotch_hash: private Hash, private KeyEqual, private GrowthPolicy {
-private:
-    template<typename U>
-    using has_mapped_type = typename std::integral_constant<bool, !std::is_same<U, void>::value>;
-    
-    static_assert(noexcept(std::declval<GrowthPolicy>().bucket_for_hash(std::size_t(0))), "GrowthPolicy::bucket_for_hash must be noexcept.");
-    static_assert(noexcept(std::declval<GrowthPolicy>().clear()), "GrowthPolicy::clear must be noexcept.");
-    
-public:
-    template<bool IsConst>
-    class hopscotch_iterator;
-    
-    using key_type = typename KeySelect::key_type;
-    using value_type = ValueType;
-    using size_type = std::size_t;
-    using difference_type = std::ptrdiff_t;
-    using hasher = Hash;
-    using key_equal = KeyEqual;
-    using allocator_type = Allocator;
-    using reference = value_type&;
-    using const_reference = const value_type&;
-    using pointer = value_type*;
-    using const_pointer = const value_type*;
-    using iterator = hopscotch_iterator<false>;
-    using const_iterator = hopscotch_iterator<true>;
-    
-private:
-    using hopscotch_bucket = tsl::detail_hopscotch_hash::hopscotch_bucket<ValueType, NeighborhoodSize, StoreHash>;
-    using neighborhood_bitmap = typename hopscotch_bucket::neighborhood_bitmap;
-    
-    using buckets_allocator = typename std::allocator_traits<allocator_type>::template rebind_alloc<hopscotch_bucket>;
-    using buckets_container_type = std::vector<hopscotch_bucket, buckets_allocator>;  
-    
-    using overflow_container_type = OverflowContainer;
-    
-    static_assert(std::is_same<typename overflow_container_type::value_type, ValueType>::value, 
-                  "OverflowContainer should have ValueType as type.");
-    
-    static_assert(std::is_same<typename overflow_container_type::allocator_type, Allocator>::value, 
-                  "Invalid allocator, not the same type as the value_type.");
-    
-    
-    using iterator_buckets = typename buckets_container_type::iterator; 
-    using const_iterator_buckets = typename buckets_container_type::const_iterator;
-    
-    using iterator_overflow = typename overflow_container_type::iterator; 
-    using const_iterator_overflow = typename overflow_container_type::const_iterator; 
-    
-public:    
-    /**
-     * The `operator*()` and `operator->()` methods return a const reference and const pointer respectively to the 
-     * stored value type.
-     * 
-     * In case of a map, to get a modifiable reference to the value associated to a key (the `.second` in the 
-     * stored pair), you have to call `value()`.
-     */
-    template<bool IsConst>
-    class hopscotch_iterator {
-        friend class hopscotch_hash;
-    private:
-        using iterator_bucket = typename std::conditional<IsConst, 
-                                                            typename hopscotch_hash::const_iterator_buckets, 
-                                                            typename hopscotch_hash::iterator_buckets>::type;
-        using iterator_overflow = typename std::conditional<IsConst, 
-                                                            typename hopscotch_hash::const_iterator_overflow, 
-                                                            typename hopscotch_hash::iterator_overflow>::type;
-    
-        
-        hopscotch_iterator(iterator_bucket buckets_iterator, iterator_bucket buckets_end_iterator, 
-                           iterator_overflow overflow_iterator) noexcept : 
-            m_buckets_iterator(buckets_iterator), m_buckets_end_iterator(buckets_end_iterator),
-            m_overflow_iterator(overflow_iterator)
-        {
-        }
-        
-    public:
-        using iterator_category = std::forward_iterator_tag;
-        using value_type = const typename hopscotch_hash::value_type;
-        using difference_type = std::ptrdiff_t;
-        using reference = value_type&;
-        using pointer = value_type*;
-        
-        
-        hopscotch_iterator() noexcept {
-        }
-        
-        // Copy constructor from iterator to const_iterator.
-        template<bool TIsConst = IsConst, typename std::enable_if<TIsConst>::type* = nullptr>
-        hopscotch_iterator(const hopscotch_iterator<!TIsConst>& other) noexcept :
-            m_buckets_iterator(other.m_buckets_iterator), m_buckets_end_iterator(other.m_buckets_end_iterator),
-            m_overflow_iterator(other.m_overflow_iterator)
-        {
-        }
-
-        hopscotch_iterator(const hopscotch_iterator& other) = default;
-        hopscotch_iterator(hopscotch_iterator&& other) = default;
-        hopscotch_iterator& operator=(const hopscotch_iterator& other) = default;
-        hopscotch_iterator& operator=(hopscotch_iterator&& other) = default;
-        
-        const typename hopscotch_hash::key_type& key() const {
-            if(m_buckets_iterator != m_buckets_end_iterator) {
-                return KeySelect()(m_buckets_iterator->value());
-            }
-            
-            return KeySelect()(*m_overflow_iterator);
-        }
-
-        template<class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-        typename std::conditional<
-                        IsConst, 
-                        const typename U::value_type&, 
-                        typename U::value_type&>::type value() const
-        {
-            if(m_buckets_iterator != m_buckets_end_iterator) {
-                return U()(m_buckets_iterator->value());
-            }
-            
-            return U()(*m_overflow_iterator);
-        }
-        
-        reference operator*() const { 
-            if(m_buckets_iterator != m_buckets_end_iterator) {
-                return m_buckets_iterator->value();
-            }
-            
-            return *m_overflow_iterator;
-        }
-        
-        pointer operator->() const { 
-            if(m_buckets_iterator != m_buckets_end_iterator) {
-                return std::addressof(m_buckets_iterator->value()); 
-            }
-            
-            return std::addressof(*m_overflow_iterator); 
-        }
-        
-        hopscotch_iterator& operator++() {
-            if(m_buckets_iterator == m_buckets_end_iterator) {
-                ++m_overflow_iterator;
-                return *this;
-            }
-            
-            do {
-                ++m_buckets_iterator;
-            } while(m_buckets_iterator != m_buckets_end_iterator && m_buckets_iterator->empty());
-            
-            return *this; 
-        }
-        
-        hopscotch_iterator operator++(int) {
-            hopscotch_iterator tmp(*this);
-            ++*this;
-            
-            return tmp;
-        }
-        
-        friend bool operator==(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) { 
-            return lhs.m_buckets_iterator == rhs.m_buckets_iterator && 
-                   lhs.m_overflow_iterator == rhs.m_overflow_iterator; 
-        }
-        
-        friend bool operator!=(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) { 
-            return !(lhs == rhs); 
-        }
-        
-    private:
-        iterator_bucket m_buckets_iterator;
-        iterator_bucket m_buckets_end_iterator;
-        iterator_overflow m_overflow_iterator;
-    };
-    
-public:
-    template<class OC = OverflowContainer, typename std::enable_if<!has_key_compare<OC>::value>::type* = nullptr>
-    hopscotch_hash(size_type bucket_count, 
-                  const Hash& hash,
-                  const KeyEqual& equal,
-                  const Allocator& alloc,
-                  float max_load_factor) :  Hash(hash),
-                                            KeyEqual(equal),
-                                            GrowthPolicy(bucket_count),
-                                            m_buckets_data(alloc), 
-                                            m_overflow_elements(alloc),
-                                            m_buckets(static_empty_bucket_ptr()),
-                                            m_nb_elements(0)
-    {
-        if(bucket_count > max_bucket_count()) {
-            throw std::length_error("The map exceeds its maxmimum size.");
-        }
-        
-        if(bucket_count > 0) {
-            static_assert(NeighborhoodSize - 1 > 0, "");
-            
-            // Can't directly construct with the appropriate size in the initializer 
-            // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8
-            m_buckets_data.resize(bucket_count + NeighborhoodSize - 1);
-            m_buckets = m_buckets_data.data();
-        }
-        
-        
-        this->max_load_factor(max_load_factor);
-        
-        
-        // Check in the constructor instead of outside of a function to avoi compilation issues
-        // when value_type is not complete.
-        static_assert(std::is_nothrow_move_constructible<value_type>::value || 
-                      std::is_copy_constructible<value_type>::value, 
-                      "value_type must be either copy constructible or nothrow move constructible.");
-    }
-    
-    template<class OC = OverflowContainer, typename std::enable_if<has_key_compare<OC>::value>::type* = nullptr>
-    hopscotch_hash(size_type bucket_count, 
-                  const Hash& hash,
-                  const KeyEqual& equal,
-                  const Allocator& alloc,
-                  float max_load_factor,
-                  const typename OC::key_compare& comp) : Hash(hash),
-                                                          KeyEqual(equal),
-                                                          GrowthPolicy(bucket_count),
-                                                          m_buckets_data(alloc), 
-                                                          m_overflow_elements(comp, alloc),
-                                                          m_buckets(static_empty_bucket_ptr()),
-                                                          m_nb_elements(0)
-    {
-        
-        if(bucket_count > max_bucket_count()) {
-            throw std::length_error("The map exceeds its maxmimum size.");
-        }
-        
-        if(bucket_count > 0) {
-            static_assert(NeighborhoodSize - 1 > 0, "");
-            
-            // Can't directly construct with the appropriate size in the initializer 
-            // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8
-            m_buckets_data.resize(bucket_count + NeighborhoodSize - 1);
-            m_buckets = m_buckets_data.data();
-        }
-        
-        
-        this->max_load_factor(max_load_factor);
-        
-        
-        // Check in the constructor instead of outside of a function to avoi compilation issues
-        // when value_type is not complete.
-        static_assert(std::is_nothrow_move_constructible<value_type>::value || 
-                      std::is_copy_constructible<value_type>::value, 
-                      "value_type must be either copy constructible or nothrow move constructible.");
-    }
-    
-    hopscotch_hash(const hopscotch_hash& other): 
-                          Hash(other),
-                          KeyEqual(other),
-                          GrowthPolicy(other),
-                          m_buckets_data(other.m_buckets_data),
-                          m_overflow_elements(other.m_overflow_elements),
-                          m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():
-                                                           m_buckets_data.data()),
-                          m_nb_elements(other.m_nb_elements),
-                          m_max_load_factor(other.m_max_load_factor),
-                          m_max_load_threshold_rehash(other.m_max_load_threshold_rehash),
-                          m_min_load_threshold_rehash(other.m_min_load_threshold_rehash) 
-    {
-    }
-    
-    hopscotch_hash(hopscotch_hash&& other) 
-                        noexcept(
-                            std::is_nothrow_move_constructible<Hash>::value &&
-                            std::is_nothrow_move_constructible<KeyEqual>::value &&
-                            std::is_nothrow_move_constructible<GrowthPolicy>::value &&
-                            std::is_nothrow_move_constructible<buckets_container_type>::value &&
-                            std::is_nothrow_move_constructible<overflow_container_type>::value
-                        ):
-                          Hash(std::move(static_cast<Hash&>(other))),
-                          KeyEqual(std::move(static_cast<KeyEqual&>(other))),
-                          GrowthPolicy(std::move(static_cast<GrowthPolicy&>(other))),
-                          m_buckets_data(std::move(other.m_buckets_data)),
-                          m_overflow_elements(std::move(other.m_overflow_elements)),
-                          m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():
-                                                           m_buckets_data.data()),
-                          m_nb_elements(other.m_nb_elements),
-                          m_max_load_factor(other.m_max_load_factor),
-                          m_max_load_threshold_rehash(other.m_max_load_threshold_rehash),
-                          m_min_load_threshold_rehash(other.m_min_load_threshold_rehash)
-    {
-        other.GrowthPolicy::clear();
-        other.m_buckets_data.clear();
-        other.m_overflow_elements.clear();
-        other.m_buckets = static_empty_bucket_ptr();
-        other.m_nb_elements = 0;
-        other.m_max_load_threshold_rehash = 0;
-        other.m_min_load_threshold_rehash = 0;
-    }
-    
-    hopscotch_hash& operator=(const hopscotch_hash& other) {
-        if(&other != this) {
-            Hash::operator=(other);
-            KeyEqual::operator=(other);
-            GrowthPolicy::operator=(other);
-            
-            m_buckets_data = other.m_buckets_data;
-            m_overflow_elements = other.m_overflow_elements;
-            m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr():
-                                               m_buckets_data.data();
-            m_nb_elements = other.m_nb_elements;
-            m_max_load_factor = other.m_max_load_factor;
-            m_max_load_threshold_rehash = other.m_max_load_threshold_rehash;
-            m_min_load_threshold_rehash = other.m_min_load_threshold_rehash;
-        }
-        
-        return *this;
-    }
-    
-    hopscotch_hash& operator=(hopscotch_hash&& other) {
-        other.swap(*this);
-        other.clear();
-        
-        return *this;
-    }
-    
-    allocator_type get_allocator() const {
-        return m_buckets_data.get_allocator();
-    }
-    
-    
-    /*
-     * Iterators
-     */
-    iterator begin() noexcept {
-        auto begin = m_buckets_data.begin();
-        while(begin != m_buckets_data.end() && begin->empty()) {
-            ++begin;
-        }
-        
-        return iterator(begin, m_buckets_data.end(), m_overflow_elements.begin());
-    }
-    
-    const_iterator begin() const noexcept {
-        return cbegin();
-    }
-    
-    const_iterator cbegin() const noexcept {
-        auto begin = m_buckets_data.cbegin();
-        while(begin != m_buckets_data.cend() && begin->empty()) {
-            ++begin;
-        }
-        
-        return const_iterator(begin, m_buckets_data.cend(), m_overflow_elements.cbegin());
-    }
-    
-    iterator end() noexcept {
-        return iterator(m_buckets_data.end(), m_buckets_data.end(), m_overflow_elements.end());
-    }
-    
-    const_iterator end() const noexcept {
-        return cend();
-    }
-    
-    const_iterator cend() const noexcept {
-        return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), m_overflow_elements.cend());
-    }
-    
-    
-    /*
-     * Capacity
-     */
-    bool empty() const noexcept {
-        return m_nb_elements == 0;
-    }
-    
-    size_type size() const noexcept {
-        return m_nb_elements;
-    }
-    
-    size_type max_size() const noexcept {
-        return m_buckets_data.max_size();
-    }
-    
-    /*
-     * Modifiers
-     */
-    void clear() noexcept {
-        for(auto& bucket: m_buckets_data) {
-            bucket.clear();
-        }
-        
-        m_overflow_elements.clear();
-        m_nb_elements = 0;
-    }
-    
-    
-    std::pair<iterator, bool> insert(const value_type& value) { 
-        return insert_impl(value); 
-    }
-        
-    template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
-    std::pair<iterator, bool> insert(P&& value) { 
-        return insert_impl(value_type(std::forward<P>(value)));
-    }
-    
-    std::pair<iterator, bool> insert(value_type&& value) { 
-        return insert_impl(std::move(value)); 
-    }
-    
-    
-    iterator insert(const_iterator hint, const value_type& value) { 
-        if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { 
-            return mutable_iterator(hint); 
-        }
-        
-        return insert(value).first; 
-    }
-        
-    template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
-    iterator insert(const_iterator hint, P&& value) {
-        return emplace_hint(hint, std::forward<P>(value)); 
-    }
-    
-    iterator insert(const_iterator hint, value_type&& value) { 
-        if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { 
-            return mutable_iterator(hint); 
-        }
-        
-        return insert(std::move(value)).first; 
-    }
-    
-    
-    template<class InputIt>
-    void insert(InputIt first, InputIt last) {
-        if(std::is_base_of<std::forward_iterator_tag, 
-                           typename std::iterator_traits<InputIt>::iterator_category>::value) 
-        {
-            const auto nb_elements_insert = std::distance(first, last);
-            const std::size_t nb_elements_in_buckets = m_nb_elements - m_overflow_elements.size();
-            const std::size_t nb_free_buckets = m_max_load_threshold_rehash - nb_elements_in_buckets;
-            tsl_hh_assert(m_nb_elements >= m_overflow_elements.size());
-            tsl_hh_assert(m_max_load_threshold_rehash >= nb_elements_in_buckets);
-            
-            if(nb_elements_insert > 0 && nb_free_buckets < std::size_t(nb_elements_insert)) {
-                reserve(nb_elements_in_buckets + std::size_t(nb_elements_insert));
-            }
-        }
-        
-        for(; first != last; ++first) {
-            insert(*first);
-        }
-    }
-    
-    
-    template<class M>
-    std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj) { 
-        return insert_or_assign_impl(k, std::forward<M>(obj)); 
-    }
-
-    template<class M>
-    std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj) { 
-        return insert_or_assign_impl(std::move(k), std::forward<M>(obj)); 
-    }
-    
-    
-    template<class M>
-    iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) {
-        if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { 
-            auto it = mutable_iterator(hint); 
-            it.value() = std::forward<M>(obj);
-            
-            return it;
-        }
-        
-        return insert_or_assign(k, std::forward<M>(obj)).first;
-    }
-    
-    template<class M>
-    iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) {
-        if(hint != cend() && compare_keys(KeySelect()(*hint), k)) {
-            auto it = mutable_iterator(hint); 
-            it.value() = std::forward<M>(obj);
-            
-            return it;
-        }
-        
-        return insert_or_assign(std::move(k), std::forward<M>(obj)).first;
-    }
-    
-    
-    template<class... Args>
-    std::pair<iterator, bool> emplace(Args&&... args) {
-        return insert(value_type(std::forward<Args>(args)...));
-    }
-    
-    template<class... Args>
-    iterator emplace_hint(const_iterator hint, Args&&... args) {
-        return insert(hint, value_type(std::forward<Args>(args)...));        
-    }
-    
-    template<class... Args>
-    std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args) { 
-        return try_emplace_impl(k, std::forward<Args>(args)...);
-    }
-    
-    template<class... Args>
-    std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args) {
-        return try_emplace_impl(std::move(k), std::forward<Args>(args)...);
-    }
-    
-    template<class... Args>
-    iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { 
-        if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { 
-            return mutable_iterator(hint); 
-        }
-        
-        return try_emplace(k, std::forward<Args>(args)...).first;
-    }
-    
-    template<class... Args>
-    iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) {
-        if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { 
-            return mutable_iterator(hint); 
-        }
-        
-        return try_emplace(std::move(k), std::forward<Args>(args)...).first;
-    }
-    
-    
-    /**
-     * Here to avoid `template<class K> size_type erase(const K& key)` being used when
-     * we use an iterator instead of a const_iterator.
-     */
-    iterator erase(iterator pos) {
-        return erase(const_iterator(pos));
-    }
-    
-    iterator erase(const_iterator pos) {
-        const std::size_t ibucket_for_hash = bucket_for_hash(hash_key(pos.key()));
-        
-        if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) {
-            auto it_bucket = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator);
-            erase_from_bucket(*it_bucket, ibucket_for_hash);
-            
-            return ++iterator(it_bucket, m_buckets_data.end(), m_overflow_elements.begin()); 
-        }
-        else {
-            auto it_next_overflow = erase_from_overflow(pos.m_overflow_iterator, ibucket_for_hash);
-            return iterator(m_buckets_data.end(), m_buckets_data.end(), it_next_overflow);
-        }
-    }
-    
-    iterator erase(const_iterator first, const_iterator last) {
-        if(first == last) {
-            return mutable_iterator(first);
-        }
-        
-        auto to_delete = erase(first);
-        while(to_delete != last) {
-            to_delete = erase(to_delete);
-        }
-        
-        return to_delete;
-    }
-    
-    template<class K>
-    size_type erase(const K& key) {
-        return erase(key, hash_key(key));
-    }
-    
-    template<class K>
-    size_type erase(const K& key, std::size_t hash) {
-        const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-
-        hopscotch_bucket* bucket_found = find_in_buckets(key, hash, m_buckets + ibucket_for_hash);
-        if(bucket_found != nullptr) {
-            erase_from_bucket(*bucket_found, ibucket_for_hash);
-
-            return 1;
-        }
-        
-        if(m_buckets[ibucket_for_hash].has_overflow()) {
-            auto it_overflow = find_in_overflow(key);
-            if(it_overflow != m_overflow_elements.end()) {
-                erase_from_overflow(it_overflow, ibucket_for_hash);
-                
-                return 1;
-            }
-        }
-        
-        return 0;
-    }
-    
-    void swap(hopscotch_hash& other) {
-        using std::swap;
-        
-        swap(static_cast<Hash&>(*this), static_cast<Hash&>(other));
-        swap(static_cast<KeyEqual&>(*this), static_cast<KeyEqual&>(other));
-        swap(static_cast<GrowthPolicy&>(*this), static_cast<GrowthPolicy&>(other));
-        swap(m_buckets_data, other.m_buckets_data);
-        swap(m_overflow_elements, other.m_overflow_elements);
-        swap(m_buckets, other.m_buckets);
-        swap(m_nb_elements, other.m_nb_elements);
-        swap(m_max_load_factor, other.m_max_load_factor);
-        swap(m_max_load_threshold_rehash, other.m_max_load_threshold_rehash);
-        swap(m_min_load_threshold_rehash, other.m_min_load_threshold_rehash);
-    }
-    
-    
-    /*
-     * Lookup
-     */
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    typename U::value_type& at(const K& key) {
-        return at(key, hash_key(key));
-    }
-    
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    typename U::value_type& at(const K& key, std::size_t hash) {
-        return const_cast<typename U::value_type&>(static_cast<const hopscotch_hash*>(this)->at(key, hash));
-    }
-    
-    
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    const typename U::value_type& at(const K& key) const {
-        return at(key, hash_key(key));
-    }
-    
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    const typename U::value_type& at(const K& key, std::size_t hash) const {
-        using T = typename U::value_type;
-        
-        const T* value = find_value_impl(key, hash, m_buckets + bucket_for_hash(hash));
-        if(value == nullptr) {
-            throw std::out_of_range("Couldn't find key.");
-        }
-        else {
-            return *value;
-        }
-    }
-    
-    
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    typename U::value_type& operator[](K&& key) {
-        using T = typename U::value_type;
-        
-        const std::size_t hash = hash_key(key);
-        const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-        
-        T* value = find_value_impl(key, hash, m_buckets + ibucket_for_hash);
-        if(value != nullptr) {
-            return *value;
-        }
-        else {
-            return insert_value(ibucket_for_hash, hash, std::piecewise_construct, 
-                                                        std::forward_as_tuple(std::forward<K>(key)), 
-                                                        std::forward_as_tuple()).first.value();
-        }
-    }
-    
-    
-    template<class K>
-    size_type count(const K& key) const {
-        return count(key, hash_key(key));
-    }
-    
-    template<class K>
-    size_type count(const K& key, std::size_t hash) const {
-        return count_impl(key, hash, m_buckets + bucket_for_hash(hash));
-    }
-    
-    
-    template<class K>
-    iterator find(const K& key) {
-        return find(key, hash_key(key));
-    }
-    
-    template<class K>
-    iterator find(const K& key, std::size_t hash) {
-        return find_impl(key, hash, m_buckets + bucket_for_hash(hash));
-    }
-    
-    
-    template<class K>
-    const_iterator find(const K& key) const {
-        return find(key, hash_key(key));
-    }
-    
-    template<class K>
-    const_iterator find(const K& key, std::size_t hash) const {
-        return find_impl(key, hash, m_buckets + bucket_for_hash(hash));
-    }
-    
-    
-    template<class K>
-    bool contains(const K& key) const {
-        return contains(key, hash_key(key));
-    }
-    
-    template<class K>
-    bool contains(const K& key, std::size_t hash) const {
-        return count(key, hash) != 0;
-    }
-    
-    
-    template<class K>
-    std::pair<iterator, iterator> equal_range(const K& key) {
-        return equal_range(key, hash_key(key));
-    }
-    
-    template<class K>
-    std::pair<iterator, iterator> equal_range(const K& key, std::size_t hash) {
-        iterator it = find(key, hash);
-        return std::make_pair(it, (it == end())?it:std::next(it));
-    }
-    
-    
-    template<class K>
-    std::pair<const_iterator, const_iterator> equal_range(const K& key) const {
-        return equal_range(key, hash_key(key));
-    }
-    
-    template<class K>
-    std::pair<const_iterator, const_iterator> equal_range(const K& key, std::size_t hash) const {
-        const_iterator it = find(key, hash);
-        return std::make_pair(it, (it == cend())?it:std::next(it));
-    }
-    
-    /*
-     * Bucket interface 
-     */
-    size_type bucket_count() const {
-        /*
-         * So that the last bucket can have NeighborhoodSize neighbors, the size of the bucket array is a little
-         * bigger than the real number of buckets when not empty. 
-         * We could use some of the buckets at the beginning, but it is faster this way as we avoid extra checks.
-         */
-        if(m_buckets_data.empty()) {
-            return 0;
-        }
-        
-        return m_buckets_data.size() - NeighborhoodSize + 1; 
-    }
-    
-    size_type max_bucket_count() const {
-        const std::size_t max_bucket_count = std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size());
-        return max_bucket_count - NeighborhoodSize + 1;
-    }
-    
-    
-    /*
-     *  Hash policy 
-     */
-    float load_factor() const {
-        if(bucket_count() == 0) {
-            return 0;
-        }
-        
-        return float(m_nb_elements)/float(bucket_count());
-    }
-    
-    float max_load_factor() const {
-        return m_max_load_factor;
-    }
-    
-    void max_load_factor(float ml) {
-        m_max_load_factor = std::max(0.1f, std::min(ml, 0.95f));
-        m_max_load_threshold_rehash = size_type(float(bucket_count())*m_max_load_factor);
-        m_min_load_threshold_rehash = size_type(float(bucket_count())*MIN_LOAD_FACTOR_FOR_REHASH);
-    }
-    
-    void rehash(size_type count_) {
-        count_ = std::max(count_, size_type(std::ceil(float(size())/max_load_factor())));
-        rehash_impl(count_);
-    }
-    
-    void reserve(size_type count_) {
-        rehash(size_type(std::ceil(float(count_)/max_load_factor())));
-    }
-    
-    
-    /*
-     * Observers
-     */
-    hasher hash_function() const {
-        return static_cast<const Hash&>(*this);
-    }
-    
-    key_equal key_eq() const {
-        return static_cast<const KeyEqual&>(*this);
-    }
-    
-    /*
-     * Other
-     */
-    iterator mutable_iterator(const_iterator pos) {
-        if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) {
-            // Get a non-const iterator
-            auto it = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator);
-            return iterator(it, m_buckets_data.end(), m_overflow_elements.begin());
-        }
-        else {
-            // Get a non-const iterator
-            auto it = mutable_overflow_iterator(pos.m_overflow_iterator);
-            return iterator(m_buckets_data.end(), m_buckets_data.end(), it);
-        }
-    }
-    
-    size_type overflow_size() const noexcept {
-        return m_overflow_elements.size();
-    }
-    
-    template<class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
-    typename U::key_compare key_comp() const {
-        return m_overflow_elements.key_comp();
-    }
-    
-    
-private:
-    template<class K>
-    std::size_t hash_key(const K& key) const {
-        return Hash::operator()(key);
-    }
-    
-    template<class K1, class K2>
-    bool compare_keys(const K1& key1, const K2& key2) const {
-        return KeyEqual::operator()(key1, key2);
-    }
-    
-    std::size_t bucket_for_hash(std::size_t hash) const {
-        const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash);
-        tsl_hh_assert(bucket < m_buckets_data.size() || (bucket == 0 && m_buckets_data.empty()));
-        
-        return bucket;
-    }
-    
-    template<typename U = value_type, 
-             typename std::enable_if<std::is_nothrow_move_constructible<U>::value>::type* = nullptr>
-    void rehash_impl(size_type count_) {
-        hopscotch_hash new_map = new_hopscotch_hash(count_);
-        
-        if(!m_overflow_elements.empty()) {
-            new_map.m_overflow_elements.swap(m_overflow_elements);
-            new_map.m_nb_elements += new_map.m_overflow_elements.size();
-            
-            for(const value_type& value : new_map.m_overflow_elements) {
-                const std::size_t ibucket_for_hash = new_map.bucket_for_hash(new_map.hash_key(KeySelect()(value)));
-                new_map.m_buckets[ibucket_for_hash].set_overflow(true);
-            }
-        }
-        
-        try {
-            const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count());
-            for(auto it_bucket = m_buckets_data.begin(); it_bucket != m_buckets_data.end(); ++it_bucket) {
-                if(it_bucket->empty()) {
-                    continue;
-                }
-                
-                const std::size_t hash = use_stored_hash?
-                                            it_bucket->truncated_bucket_hash():
-                                            new_map.hash_key(KeySelect()(it_bucket->value()));
-                const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);
-                
-                new_map.insert_value(ibucket_for_hash, hash, std::move(it_bucket->value()));
-                
-                
-                erase_from_bucket(*it_bucket, bucket_for_hash(hash));
-            }
-        } 
-        /*
-         * The call to insert_value may throw an exception if an element is added to the overflow
-         * list. Rollback the elements in this case.
-         */
-        catch(...) {
-            m_overflow_elements.swap(new_map.m_overflow_elements);
-            
-            const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count());
-            for(auto it_bucket = new_map.m_buckets_data.begin(); it_bucket != new_map.m_buckets_data.end(); ++it_bucket) {
-                if(it_bucket->empty()) {
-                    continue;
-                }
-                
-                const std::size_t hash = use_stored_hash?
-                                            it_bucket->truncated_bucket_hash():
-                                            hash_key(KeySelect()(it_bucket->value()));
-                const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-                
-                // The elements we insert were not in the overflow list before the switch.
-                // They will not be go in the overflow list if we rollback the switch.
-                insert_value(ibucket_for_hash, hash, std::move(it_bucket->value()));
-            }
-            
-            throw;
-        }
-        
-        new_map.swap(*this);
-    }
-    
-    template<typename U = value_type, 
-             typename std::enable_if<std::is_copy_constructible<U>::value && 
-                                     !std::is_nothrow_move_constructible<U>::value>::type* = nullptr>
-    void rehash_impl(size_type count_) {
-        hopscotch_hash new_map = new_hopscotch_hash(count_);
-                
-        const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count());
-        for(const hopscotch_bucket& bucket: m_buckets_data) {
-            if(bucket.empty()) {
-                continue;
-            }
-            
-            const std::size_t hash = use_stored_hash?
-                                         bucket.truncated_bucket_hash():
-                                         new_map.hash_key(KeySelect()(bucket.value()));
-            const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);
-            
-            new_map.insert_value(ibucket_for_hash, hash, bucket.value());
-        }
-        
-        for(const value_type& value: m_overflow_elements) {
-            const std::size_t hash = new_map.hash_key(KeySelect()(value));
-            const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);
-            
-            new_map.insert_value(ibucket_for_hash, hash, value);
-        }
-            
-        new_map.swap(*this);
-    }
-    
-#ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR
-    iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) {
-        return std::next(m_overflow_elements.begin(), std::distance(m_overflow_elements.cbegin(), it));        
-    }
-#else            
-    iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) {
-        return m_overflow_elements.erase(it, it);       
-    }
-#endif    
-
-    // iterator is in overflow list
-    iterator_overflow erase_from_overflow(const_iterator_overflow pos, std::size_t ibucket_for_hash) {
-#ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR        
-        auto it_next = m_overflow_elements.erase(mutable_overflow_iterator(pos));
-#else
-        auto it_next = m_overflow_elements.erase(pos);
-#endif
-        m_nb_elements--;
-        
-        
-        // Check if we can remove the overflow flag
-        tsl_hh_assert(m_buckets[ibucket_for_hash].has_overflow());
-        for(const value_type& value: m_overflow_elements) {
-            const std::size_t bucket_for_value = bucket_for_hash(hash_key(KeySelect()(value)));
-            if(bucket_for_value == ibucket_for_hash) {
-                return it_next;
-            }
-        }
-        
-        m_buckets[ibucket_for_hash].set_overflow(false);
-        return it_next;
-    }
-    
-
-    /**
-     * bucket_for_value is the bucket in which the value is.
-     * ibucket_for_hash is the bucket where the value belongs.
-     */
-    void erase_from_bucket(hopscotch_bucket& bucket_for_value, std::size_t ibucket_for_hash) noexcept {
-        const std::size_t ibucket_for_value = std::distance(m_buckets_data.data(), &bucket_for_value);
-        tsl_hh_assert(ibucket_for_value >= ibucket_for_hash);
-        
-        bucket_for_value.remove_value();
-        m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_for_value - ibucket_for_hash);
-        m_nb_elements--;
-    }
-    
-
-    
-    template<class K, class M>
-    std::pair<iterator, bool> insert_or_assign_impl(K&& key, M&& obj) {
-        auto it = try_emplace_impl(std::forward<K>(key), std::forward<M>(obj));
-        if(!it.second) {
-            it.first.value() = std::forward<M>(obj);
-        }
-        
-        return it;
-    }
-    
-    template<typename P, class... Args>
-    std::pair<iterator, bool> try_emplace_impl(P&& key, Args&&... args_value) {
-        const std::size_t hash = hash_key(key);
-        const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-        
-        // Check if already presents
-        auto it_find = find_impl(key, hash, m_buckets + ibucket_for_hash);
-        if(it_find != end()) {
-            return std::make_pair(it_find, false);
-        }
-        
-        return insert_value(ibucket_for_hash, hash, std::piecewise_construct, 
-                                                    std::forward_as_tuple(std::forward<P>(key)), 
-                                                    std::forward_as_tuple(std::forward<Args>(args_value)...));
-    }
-    
-    template<typename P>
-    std::pair<iterator, bool> insert_impl(P&& value) {
-        const std::size_t hash = hash_key(KeySelect()(value));
-        const std::size_t ibucket_for_hash = bucket_for_hash(hash);
-        
-        // Check if already presents
-        auto it_find = find_impl(KeySelect()(value), hash, m_buckets + ibucket_for_hash);
-        if(it_find != end()) {
-            return std::make_pair(it_find, false);
-        }
-        
-        
-        return insert_value(ibucket_for_hash, hash, std::forward<P>(value));
-    }
-    
-    template<typename... Args>
-    std::pair<iterator, bool> insert_value(std::size_t ibucket_for_hash, std::size_t hash, Args&&... value_type_args) {
-        if((m_nb_elements - m_overflow_elements.size()) >= m_max_load_threshold_rehash) {
-            rehash(GrowthPolicy::next_bucket_count());
-            ibucket_for_hash = bucket_for_hash(hash);
-        }
-        
-        std::size_t ibucket_empty = find_empty_bucket(ibucket_for_hash);
-        if(ibucket_empty < m_buckets_data.size()) {
-            do {
-                tsl_hh_assert(ibucket_empty >= ibucket_for_hash);
-                
-                // Empty bucket is in range of NeighborhoodSize, use it
-                if(ibucket_empty - ibucket_for_hash < NeighborhoodSize) {
-                    auto it = insert_in_bucket(ibucket_empty, ibucket_for_hash, 
-                                               hash, std::forward<Args>(value_type_args)...);
-                    return std::make_pair(iterator(it, m_buckets_data.end(), m_overflow_elements.begin()), true);
-                }
-            }
-            // else, try to swap values to get a closer empty bucket
-            while(swap_empty_bucket_closer(ibucket_empty));
-        }
-            
-        // Load factor is too low or a rehash will not change the neighborhood, put the value in overflow list
-        if(size() < m_min_load_threshold_rehash || !will_neighborhood_change_on_rehash(ibucket_for_hash)) {
-            auto it = insert_in_overflow(ibucket_for_hash, std::forward<Args>(value_type_args)...);
-            return std::make_pair(iterator(m_buckets_data.end(), m_buckets_data.end(), it), true);
-        }
-    
-        rehash(GrowthPolicy::next_bucket_count());
-        ibucket_for_hash = bucket_for_hash(hash);
-        
-        return insert_value(ibucket_for_hash, hash, std::forward<Args>(value_type_args)...);
-    }    
-    
-    /*
-     * Return true if a rehash will change the position of a key-value in the neighborhood of 
-     * ibucket_neighborhood_check. In this case a rehash is needed instead of puting the value in overflow list.
-     */
-    bool will_neighborhood_change_on_rehash(size_t ibucket_neighborhood_check) const {
-        std::size_t expand_bucket_count = GrowthPolicy::next_bucket_count();
-        GrowthPolicy expand_growth_policy(expand_bucket_count);
-        
-        const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(expand_bucket_count);
-        for(size_t ibucket = ibucket_neighborhood_check; 
-            ibucket < m_buckets_data.size() && (ibucket - ibucket_neighborhood_check) < NeighborhoodSize; 
-            ++ibucket)
-        {
-            tsl_hh_assert(!m_buckets[ibucket].empty());
-            
-            const size_t hash = use_stored_hash?
-                                    m_buckets[ibucket].truncated_bucket_hash():
-                                    hash_key(KeySelect()(m_buckets[ibucket].value()));
-            if(bucket_for_hash(hash) != expand_growth_policy.bucket_for_hash(hash)) {
-                return true;
-            }
-        }
-        
-        return false;
-    }
-    
-    /*
-     * Return the index of an empty bucket in m_buckets_data.
-     * If none, the returned index equals m_buckets_data.size()
-     */
-    std::size_t find_empty_bucket(std::size_t ibucket_start) const {
-        const std::size_t limit = std::min(ibucket_start + MAX_PROBES_FOR_EMPTY_BUCKET, m_buckets_data.size());
-        for(; ibucket_start < limit; ibucket_start++) {
-            if(m_buckets[ibucket_start].empty()) {
-                return ibucket_start;
-            }
-        }
-        
-        return m_buckets_data.size();
-    }
-    
-    /*
-     * Insert value in ibucket_empty where value originally belongs to ibucket_for_hash
-     * 
-     * Return bucket iterator to ibucket_empty
-     */
-    template<typename... Args>
-    iterator_buckets insert_in_bucket(std::size_t ibucket_empty, std::size_t ibucket_for_hash,
-                                      std::size_t hash, Args&&... value_type_args) 
-    {
-        tsl_hh_assert(ibucket_empty >= ibucket_for_hash );
-        tsl_hh_assert(m_buckets[ibucket_empty].empty());
-        m_buckets[ibucket_empty].set_value_of_empty_bucket(hopscotch_bucket::truncate_hash(hash), std::forward<Args>(value_type_args)...);
-        
-        tsl_hh_assert(!m_buckets[ibucket_for_hash].empty());
-        m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_empty - ibucket_for_hash);
-        m_nb_elements++;
-        
-        return m_buckets_data.begin() + ibucket_empty;
-    }
-    
-    template<class... Args, class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
-    iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) {
-        auto it = m_overflow_elements.emplace(m_overflow_elements.end(), std::forward<Args>(value_type_args)...);
-        
-        m_buckets[ibucket_for_hash].set_overflow(true);
-        m_nb_elements++;
-            
-        return it;
-    }
-    
-    template<class... Args, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
-    iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) {
-        auto it = m_overflow_elements.emplace(std::forward<Args>(value_type_args)...).first;
-        
-        m_buckets[ibucket_for_hash].set_overflow(true);
-        m_nb_elements++;
-        
-        return it;
-    }
-    
-    /*
-     * Try to swap the bucket ibucket_empty_in_out with a bucket preceding it while keeping the neighborhood 
-     * conditions correct.
-     * 
-     * If a swap was possible, the position of ibucket_empty_in_out will be closer to 0 and true will re returned.
-     */
-    bool swap_empty_bucket_closer(std::size_t& ibucket_empty_in_out) {
-        tsl_hh_assert(ibucket_empty_in_out >= NeighborhoodSize);
-        const std::size_t neighborhood_start = ibucket_empty_in_out - NeighborhoodSize + 1;
-        
-        for(std::size_t to_check = neighborhood_start; to_check < ibucket_empty_in_out; to_check++) {
-            neighborhood_bitmap neighborhood_infos = m_buckets[to_check].neighborhood_infos();
-            std::size_t to_swap = to_check;
-            
-            while(neighborhood_infos != 0 && to_swap < ibucket_empty_in_out) {
-                if((neighborhood_infos & 1) == 1) {
-                    tsl_hh_assert(m_buckets[ibucket_empty_in_out].empty());
-                    tsl_hh_assert(!m_buckets[to_swap].empty());
-                    
-                    m_buckets[to_swap].swap_value_into_empty_bucket(m_buckets[ibucket_empty_in_out]);
-                    
-                    tsl_hh_assert(!m_buckets[to_check].check_neighbor_presence(ibucket_empty_in_out - to_check));
-                    tsl_hh_assert(m_buckets[to_check].check_neighbor_presence(to_swap - to_check));
-                    
-                    m_buckets[to_check].toggle_neighbor_presence(ibucket_empty_in_out - to_check);
-                    m_buckets[to_check].toggle_neighbor_presence(to_swap - to_check);
-                    
-                    
-                    ibucket_empty_in_out = to_swap;
-                    
-                    return true;
-                }
-                
-                to_swap++;
-                neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1);
-            }
-        }
-        
-        return false;
-    }
-    
-    
-    
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    typename U::value_type* find_value_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) {
-        return const_cast<typename U::value_type*>(
-                    static_cast<const hopscotch_hash*>(this)->find_value_impl(key, hash, bucket_for_hash));
-    }
-    
-    /*
-     * Avoid the creation of an iterator to just get the value for operator[] and at() in maps. Faster this way.
-     *
-     * Return null if no value for the key (TODO use std::optional when available).
-     */
-    template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
-    const typename U::value_type* find_value_impl(const K& key, std::size_t hash, 
-                                                  const hopscotch_bucket* bucket_for_hash) const 
-    {
-        const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash);
-        if(bucket_found != nullptr) {
-            return std::addressof(ValueSelect()(bucket_found->value()));
-        }
-        
-        if(bucket_for_hash->has_overflow()) {
-            auto it_overflow = find_in_overflow(key);
-            if(it_overflow != m_overflow_elements.end()) {
-                return std::addressof(ValueSelect()(*it_overflow));
-            }
-        }
-        
-        return nullptr;
-    }
-    
-    template<class K>
-    size_type count_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const {
-        if(find_in_buckets(key, hash, bucket_for_hash) != nullptr) {
-            return 1;
-        }
-        else if(bucket_for_hash->has_overflow() && find_in_overflow(key) != m_overflow_elements.cend()) {
-            return 1;
-        }
-        else {
-            return 0;
-        }
-    }
-    
-    template<class K>
-    iterator find_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) {
-        hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash);
-        if(bucket_found != nullptr) {
-            return iterator(m_buckets_data.begin() + std::distance(m_buckets_data.data(), bucket_found), 
-                            m_buckets_data.end(), m_overflow_elements.begin());
-        }
-        
-        if(!bucket_for_hash->has_overflow()) {
-            return end();
-        }
-        
-        return iterator(m_buckets_data.end(), m_buckets_data.end(), find_in_overflow(key));
-    }
-    
-    template<class K>
-    const_iterator find_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const {
-        const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash);
-        if(bucket_found != nullptr) {
-            return const_iterator(m_buckets_data.cbegin() + std::distance(m_buckets_data.data(), bucket_found), 
-                                  m_buckets_data.cend(), m_overflow_elements.cbegin());
-        }
-        
-        if(!bucket_for_hash->has_overflow()) {
-            return cend();
-        }
-
-        
-        return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), find_in_overflow(key));
-    }
-    
-    template<class K>
-    hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) {   
-        const hopscotch_bucket* bucket_found = 
-                                    static_cast<const hopscotch_hash*>(this)->find_in_buckets(key, hash, bucket_for_hash); 
-        return const_cast<hopscotch_bucket*>(bucket_found);
-    }
-
-    
-    /**
-     * Return a pointer to the bucket which has the value, nullptr otherwise.
-     */
-    template<class K>
-    const hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const {      
-        (void) hash; // Avoid warning of unused variable when StoreHash is false;
-
-        // TODO Try to optimize the function. 
-        // I tried to use ffs and  __builtin_ffs functions but I could not reduce the time the function
-        // takes with -march=native
-        
-        neighborhood_bitmap neighborhood_infos = bucket_for_hash->neighborhood_infos();
-        while(neighborhood_infos != 0) {
-            if((neighborhood_infos & 1) == 1) {
-                // Check StoreHash before calling bucket_hash_equal. Functionally it doesn't change anythin. 
-                // If StoreHash is false, bucket_hash_equal is a no-op. Avoiding the call is there to help 
-                // GCC optimizes `hash` parameter away, it seems to not be able to do without this hint.
-                if((!StoreHash || bucket_for_hash->bucket_hash_equal(hash)) && 
-                    compare_keys(KeySelect()(bucket_for_hash->value()), key)) 
-                {
-                    return bucket_for_hash;
-                }
-            }
-            
-            ++bucket_for_hash;
-            neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1);
-        }
-        
-        return nullptr;
-    }
-    
-
-    
-    template<class K, class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
-    iterator_overflow find_in_overflow(const K& key) {
-        return std::find_if(m_overflow_elements.begin(), m_overflow_elements.end(), 
-                            [&](const value_type& value) { 
-                                return compare_keys(key, KeySelect()(value)); 
-                            });
-    }
-    
-    template<class K, class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
-    const_iterator_overflow find_in_overflow(const K& key) const {
-        return std::find_if(m_overflow_elements.cbegin(), m_overflow_elements.cend(), 
-                            [&](const value_type& value) { 
-                                return compare_keys(key, KeySelect()(value)); 
-                            });
-    }
-    
-    template<class K, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
-    iterator_overflow find_in_overflow(const K& key) {
-        return m_overflow_elements.find(key);
-    }
-    
-    template<class K, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
-    const_iterator_overflow find_in_overflow(const K& key) const {
-        return m_overflow_elements.find(key);
-    }
-    
-    
-    
-    template<class U = OverflowContainer, typename std::enable_if<!has_key_compare<U>::value>::type* = nullptr>
-    hopscotch_hash new_hopscotch_hash(size_type bucket_count) {
-        return hopscotch_hash(bucket_count, static_cast<Hash&>(*this), static_cast<KeyEqual&>(*this), 
-                              get_allocator(), m_max_load_factor);
-    }
-    
-    template<class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>
-    hopscotch_hash new_hopscotch_hash(size_type bucket_count) {
-        return hopscotch_hash(bucket_count, static_cast<Hash&>(*this), static_cast<KeyEqual&>(*this), 
-                              get_allocator(), m_max_load_factor, m_overflow_elements.key_comp());
-    }
-    
-public:    
-    static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0;
-    static constexpr float DEFAULT_MAX_LOAD_FACTOR = (NeighborhoodSize <= 30)?0.8f:0.9f;
-    
-private:    
-    static const std::size_t MAX_PROBES_FOR_EMPTY_BUCKET = 12*NeighborhoodSize;
-    static constexpr float MIN_LOAD_FACTOR_FOR_REHASH = 0.1f;
-    
-    /**
-     * We can only use the hash on rehash if the size of the hash type is the same as the stored one or
-     * if we use a power of two modulo. In the case of the power of two modulo, we just mask
-     * the least significant bytes, we just have to check that the truncated_hash_type didn't truncated
-     * too much bytes.
-     */
-    template<class T = size_type, typename std::enable_if<std::is_same<T, truncated_hash_type>::value>::type* = nullptr>
-    static bool USE_STORED_HASH_ON_REHASH(size_type /*bucket_count*/) {
-        return StoreHash;
-    }
-    
-    template<class T = size_type, typename std::enable_if<!std::is_same<T, truncated_hash_type>::value>::type* = nullptr>
-    static bool USE_STORED_HASH_ON_REHASH(size_type bucket_count) {
-        (void) bucket_count;
-        if(StoreHash && is_power_of_two_policy<GrowthPolicy>::value) {
-            tsl_hh_assert(bucket_count > 0);
-            return (bucket_count - 1) <= std::numeric_limits<truncated_hash_type>::max();
-        }
-        else {
-            return false;   
-        }
-    }
-    
-    /**
-     * Return an always valid pointer to an static empty hopscotch_bucket.
-     */            
-    hopscotch_bucket* static_empty_bucket_ptr() {
-        static hopscotch_bucket empty_bucket;
-        return &empty_bucket;
-    }
-    
-private:    
-    buckets_container_type m_buckets_data;
-    overflow_container_type m_overflow_elements;
-    
-    /**
-     * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr.
-     * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying 
-     * to find an element.
-     * 
-     * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the hopscotch_hash object.
-     */
-    hopscotch_bucket* m_buckets;
-    
-    size_type m_nb_elements;
-    
-    float m_max_load_factor;
-    
-    /**
-     * Max size of the hash table before a rehash occurs automatically to grow the table.
-     */
-    size_type m_max_load_threshold_rehash;
-    
-    /**
-     * Min size of the hash table before a rehash can occurs automatically (except if m_max_load_threshold_rehash os reached).
-     * If the neighborhood of a bucket is full before the min is reacher, the elements are put into m_overflow_elements.
-     */
-    size_type m_min_load_threshold_rehash;
-};
-
-} // end namespace detail_hopscotch_hash
-
-
-} // end namespace tsl
-
-#endif
diff --git a/modelling/src/hopscotch_map.h b/modelling/src/hopscotch_map.h
deleted file mode 100644
index f5e38af00d7c39db228a6f5f9168857af491ee40..0000000000000000000000000000000000000000
--- a/modelling/src/hopscotch_map.h
+++ /dev/null
@@ -1,716 +0,0 @@
-//////////////////////////////////////////////////////
-// SOURCE: https://github.com/Tessil/hopscotch-map  //
-// COMMIT: 4d0cccd3b41294f843925e495117919a53aa61a0 //
-// WE ASSUME THAT NO UPDATES ARE REQUIRED           //
-//////////////////////////////////////////////////////
-
-/**
- * MIT License
- * 
- * Copyright (c) 2017 Tessil
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- * 
- * 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef TSL_HOPSCOTCH_MAP_H
-#define TSL_HOPSCOTCH_MAP_H
-
-
-#include <algorithm>
-#include <cstddef>
-#include <functional>
-#include <initializer_list>
-#include <list>
-#include <memory>
-#include <type_traits>
-#include <utility>
-#include "hopscotch_hash.h"
-
-
-namespace tsl {
-
-/**
- * Implementation of a hash map using the hopscotch hashing algorithm.
- * 
- * The Key and the value T must be either nothrow move-constructible, copy-constuctible or both.
- * 
- * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false.
- * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting
- * the NeighborhoodSize to <= 30. There is no memory usage difference between 
- * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'.
- * 
- * Storing the hash may improve performance on insert during the rehash process if the hash takes time
- * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss).
- * If used with simple Hash and KeyEqual it may slow things down.
- * 
- * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy.
- * 
- * GrowthPolicy defines how the map grows and consequently how a hash value is mapped to a bucket. 
- * By default the map uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets 
- * to a power of two and uses a mask to map the hash to a bucket instead of the slow modulo.
- * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface.
- * 
- * If the destructors of Key or T throw an exception, behaviour of the class is undefined.
- * 
- * Iterators invalidation:
- *  - clear, operator=, reserve, rehash: always invalidate the iterators.
- *  - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators 
- *    if a displacement is needed to resolve a collision (which mean that most of the time, 
- *    insert will invalidate the iterators). Or if there is a rehash.
- *  - erase: iterator on the erased element is the only one which become invalid.
- */
-template<class Key, 
-         class T, 
-         class Hash = std::hash<Key>,
-         class KeyEqual = std::equal_to<Key>,
-         class Allocator = std::allocator<std::pair<Key, T>>,
-         unsigned int NeighborhoodSize = 62,
-         bool StoreHash = false,
-         class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>>
-class hopscotch_map {
-private:    
-    template<typename U>
-    using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent<U>;
-    
-    class KeySelect {
-    public:
-        using key_type = Key;
-        
-        const key_type& operator()(const std::pair<Key, T>& key_value) const {
-            return key_value.first;
-        }
-        
-        key_type& operator()(std::pair<Key, T>& key_value) {
-            return key_value.first;
-        }
-    };  
-    
-    class ValueSelect {
-    public:
-        using value_type = T;
-        
-        const value_type& operator()(const std::pair<Key, T>& key_value) const {
-            return key_value.second;
-        }
-        
-        value_type& operator()(std::pair<Key, T>& key_value) {
-            return key_value.second;
-        }
-    };
-    
-    
-    using overflow_container_type = std::list<std::pair<Key, T>, Allocator>;
-    using ht = detail_hopscotch_hash::hopscotch_hash<std::pair<Key, T>, KeySelect, ValueSelect,
-                                                     Hash, KeyEqual, 
-                                                     Allocator, NeighborhoodSize, 
-                                                     StoreHash, GrowthPolicy,
-                                                     overflow_container_type>;
-    
-public:
-    using key_type = typename ht::key_type;
-    using mapped_type = T;
-    using value_type = typename ht::value_type;
-    using size_type = typename ht::size_type;
-    using difference_type = typename ht::difference_type;
-    using hasher = typename ht::hasher;
-    using key_equal = typename ht::key_equal;
-    using allocator_type = typename ht::allocator_type;
-    using reference = typename ht::reference;
-    using const_reference = typename ht::const_reference;
-    using pointer = typename ht::pointer;
-    using const_pointer = typename ht::const_pointer;
-    using iterator = typename ht::iterator;
-    using const_iterator = typename ht::const_iterator;
-    
-    
-    
-    /*
-     * Constructors
-     */
-    hopscotch_map() : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) {
-    }
-    
-    explicit hopscotch_map(size_type bucket_count, 
-                        const Hash& hash = Hash(),
-                        const KeyEqual& equal = KeyEqual(),
-                        const Allocator& alloc = Allocator()) : 
-                        m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR)
-    {
-    }
-    
-    hopscotch_map(size_type bucket_count,
-                  const Allocator& alloc) : hopscotch_map(bucket_count, Hash(), KeyEqual(), alloc)
-    {
-    }
-    
-    hopscotch_map(size_type bucket_count,
-                  const Hash& hash,
-                  const Allocator& alloc) : hopscotch_map(bucket_count, hash, KeyEqual(), alloc)
-    {
-    }
-    
-    explicit hopscotch_map(const Allocator& alloc) : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) {
-    }
-    
-    template<class InputIt>
-    hopscotch_map(InputIt first, InputIt last,
-                size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE,
-                const Hash& hash = Hash(),
-                const KeyEqual& equal = KeyEqual(),
-                const Allocator& alloc = Allocator()) : hopscotch_map(bucket_count, hash, equal, alloc)
-    {
-        insert(first, last);
-    }
-    
-    template<class InputIt>
-    hopscotch_map(InputIt first, InputIt last,
-                size_type bucket_count,
-                const Allocator& alloc) : hopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc)
-    {
-    }
-    
-    template<class InputIt>
-    hopscotch_map(InputIt first, InputIt last,
-                size_type bucket_count,
-                const Hash& hash,
-                const Allocator& alloc) : hopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc)
-    {
-    }
-
-    hopscotch_map(std::initializer_list<value_type> init,
-                    size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE,
-                    const Hash& hash = Hash(),
-                    const KeyEqual& equal = KeyEqual(),
-                    const Allocator& alloc = Allocator()) : 
-                    hopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc)
-    {
-    }
-
-    hopscotch_map(std::initializer_list<value_type> init,
-                    size_type bucket_count,
-                    const Allocator& alloc) : 
-                    hopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc)
-    {
-    }
-
-    hopscotch_map(std::initializer_list<value_type> init,
-                    size_type bucket_count,
-                    const Hash& hash,
-                    const Allocator& alloc) : 
-                    hopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc)
-    {
-    }
-
-    
-    hopscotch_map& operator=(std::initializer_list<value_type> ilist) {
-        m_ht.clear();
-        
-        m_ht.reserve(ilist.size());
-        m_ht.insert(ilist.begin(), ilist.end());
-        
-        return *this;
-    }
-    
-    allocator_type get_allocator() const { return m_ht.get_allocator(); }
-    
-    
-    /*
-     * Iterators
-     */
-    iterator begin() noexcept { return m_ht.begin(); }
-    const_iterator begin() const noexcept { return m_ht.begin(); }
-    const_iterator cbegin() const noexcept { return m_ht.cbegin(); }
-    
-    iterator end() noexcept { return m_ht.end(); }
-    const_iterator end() const noexcept { return m_ht.end(); }
-    const_iterator cend() const noexcept { return m_ht.cend(); }
-    
-    
-    /*
-     * Capacity
-     */
-    bool empty() const noexcept { return m_ht.empty(); }
-    size_type size() const noexcept { return m_ht.size(); }
-    size_type max_size() const noexcept { return m_ht.max_size(); }
-    
-    /*
-     * Modifiers
-     */
-    void clear() noexcept { m_ht.clear(); }
-    
-    
-    
-    
-    std::pair<iterator, bool> insert(const value_type& value) { 
-        return m_ht.insert(value); 
-    }
-        
-    template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
-    std::pair<iterator, bool> insert(P&& value) { 
-        return m_ht.insert(std::forward<P>(value)); 
-    }
-    
-    std::pair<iterator, bool> insert(value_type&& value) { 
-        return m_ht.insert(std::move(value)); 
-    }
-    
-    
-    iterator insert(const_iterator hint, const value_type& value) { 
-        return m_ht.insert(hint, value); 
-    }
-        
-    template<class P, typename std::enable_if<std::is_constructible<value_type, P&&>::value>::type* = nullptr>
-    iterator insert(const_iterator hint, P&& value) { 
-        return m_ht.insert(hint, std::forward<P>(value));
-    }
-    
-    iterator insert(const_iterator hint, value_type&& value) { 
-        return m_ht.insert(hint, std::move(value)); 
-    }
-    
-    
-    template<class InputIt>
-    void insert(InputIt first, InputIt last) { 
-        m_ht.insert(first, last); 
-    }
-    
-    void insert(std::initializer_list<value_type> ilist) { 
-        m_ht.insert(ilist.begin(), ilist.end()); 
-    }
-
-    
-    
-    
-    template<class M>
-    std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj) { 
-        return m_ht.insert_or_assign(k, std::forward<M>(obj)); 
-    }
-
-    template<class M>
-    std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj) { 
-        return m_ht.insert_or_assign(std::move(k), std::forward<M>(obj)); 
-    }
-    
-    template<class M>
-    iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) {
-        return m_ht.insert_or_assign(hint, k, std::forward<M>(obj));
-    }
-    
-    template<class M>
-    iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) {
-        return m_ht.insert_or_assign(hint, std::move(k), std::forward<M>(obj));
-    }
-    
-    
-    
-    
-    /**
-     * Due to the way elements are stored, emplace will need to move or copy the key-value once.
-     * The method is equivalent to insert(value_type(std::forward<Args>(args)...));
-     * 
-     * Mainly here for compatibility with the std::unordered_map interface.
-     */
-    template<class... Args>
-    std::pair<iterator, bool> emplace(Args&&... args) { 
-        return m_ht.emplace(std::forward<Args>(args)...); 
-    }
-    
-    
-    
-    
-    /**
-     * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once.
-     * The method is equivalent to insert(hint, value_type(std::forward<Args>(args)...));
-     * 
-     * Mainly here for compatibility with the std::unordered_map interface.
-     */
-    template<class... Args>
-    iterator emplace_hint(const_iterator hint, Args&&... args) {
-        return m_ht.emplace_hint(hint, std::forward<Args>(args)...);
-    }
-    
-    
-    
-    
-    template<class... Args>
-    std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args) { 
-        return m_ht.try_emplace(k, std::forward<Args>(args)...);
-    }
-    
-    template<class... Args>
-    std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args) {
-        return m_ht.try_emplace(std::move(k), std::forward<Args>(args)...);
-    }
-    
-    template<class... Args>
-    iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) {
-        return m_ht.try_emplace(hint, k, std::forward<Args>(args)...);
-    }
-    
-    template<class... Args>
-    iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) {
-        return m_ht.try_emplace(hint, std::move(k), std::forward<Args>(args)...);
-    }
-    
-    
-
-    
-    iterator erase(iterator pos) { return m_ht.erase(pos); }
-    iterator erase(const_iterator pos) { return m_ht.erase(pos); }
-    iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); }
-    size_type erase(const key_type& key) { return m_ht.erase(key); }
-    
-    /**
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash.
-     */    
-    size_type erase(const key_type& key, std::size_t precalculated_hash) { 
-        return m_ht.erase(key, precalculated_hash); 
-    }
-    
-    /**
-     * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 
-     * If so, K must be hashable and comparable to Key.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    size_type erase(const K& key) { return m_ht.erase(key); }
-    
-    /**
-     * @copydoc erase(const K& key)
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash.
-     */    
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    size_type erase(const K& key, std::size_t precalculated_hash) { 
-        return m_ht.erase(key, precalculated_hash); 
-    }
-    
-    
-    
-    
-    void swap(hopscotch_map& other) { other.m_ht.swap(m_ht); }
-    
-    /*
-     * Lookup
-     */
-    T& at(const Key& key) { return m_ht.at(key); }
-    
-    /**
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); }
-    
-    
-    const T& at(const Key& key) const { return m_ht.at(key); }
-    
-    /**
-     * @copydoc at(const Key& key, std::size_t precalculated_hash)
-     */
-    const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); }
-    
-    
-    /**
-     * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 
-     * If so, K must be hashable and comparable to Key.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    T& at(const K& key) { return m_ht.at(key); }
-
-    /**
-     * @copydoc at(const K& key)
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */    
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); }
-    
-    
-    /**
-     * @copydoc at(const K& key)
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    const T& at(const K& key) const { return m_ht.at(key); }
-    
-    /**
-     * @copydoc at(const K& key, std::size_t precalculated_hash)
-     */    
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); }
-    
-    
-    
-    
-    T& operator[](const Key& key) { return m_ht[key]; }    
-    T& operator[](Key&& key) { return m_ht[std::move(key)]; }
-    
-    
-    
-    
-    size_type count(const Key& key) const { return m_ht.count(key); }
-    
-    /**
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    size_type count(const Key& key, std::size_t precalculated_hash) const { 
-        return m_ht.count(key, precalculated_hash); 
-    }
-    
-    /**
-     * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 
-     * If so, K must be hashable and comparable to Key.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    size_type count(const K& key) const { return m_ht.count(key); }
-    
-    /**
-     * @copydoc count(const K& key) const
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */     
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); }
-    
-    
-    
-    
-    iterator find(const Key& key) { return m_ht.find(key); }
-    
-    /**
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); }
-    
-    const_iterator find(const Key& key) const { return m_ht.find(key); }
-    
-    /**
-     * @copydoc find(const Key& key, std::size_t precalculated_hash)
-     */
-    const_iterator find(const Key& key, std::size_t precalculated_hash) const { 
-        return m_ht.find(key, precalculated_hash);
-    }
-    
-    /**
-     * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 
-     * If so, K must be hashable and comparable to Key.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    iterator find(const K& key) { return m_ht.find(key); }
-    
-    /**
-     * @copydoc find(const K& key)
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); }
-    
-    /**
-     * @copydoc find(const K& key)
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    const_iterator find(const K& key) const { return m_ht.find(key); }
-    
-    /**
-     * @copydoc find(const K& key)
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    const_iterator find(const K& key, std::size_t precalculated_hash) const { 
-        return m_ht.find(key, precalculated_hash); 
-    }
-    
-    
-    
-    
-    bool contains(const Key& key) const { return m_ht.contains(key); }
-    
-    /**
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    bool contains(const Key& key, std::size_t precalculated_hash) const { 
-        return m_ht.contains(key, precalculated_hash); 
-    }
-    
-    /**
-     * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 
-     * If so, K must be hashable and comparable to Key.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    bool contains(const K& key) const { return m_ht.contains(key); }
-    
-    /**
-     * @copydoc contains(const K& key) const
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    bool contains(const K& key, std::size_t precalculated_hash) const { 
-        return m_ht.contains(key, precalculated_hash); 
-    }
-    
-    
-    
-    
-    std::pair<iterator, iterator> equal_range(const Key& key) { return m_ht.equal_range(key); }
-    
-    /**
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    std::pair<iterator, iterator> equal_range(const Key& key, std::size_t precalculated_hash) { 
-        return m_ht.equal_range(key, precalculated_hash); 
-    }
-    
-    std::pair<const_iterator, const_iterator> equal_range(const Key& key) const { return m_ht.equal_range(key); }
-    
-    /**
-     * @copydoc equal_range(const Key& key, std::size_t precalculated_hash)
-     */
-    std::pair<const_iterator, const_iterator> equal_range(const Key& key, std::size_t precalculated_hash) const { 
-        return m_ht.equal_range(key, precalculated_hash); 
-    }
-
-    /**
-     * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 
-     * If so, K must be hashable and comparable to Key.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    std::pair<iterator, iterator> equal_range(const K& key) { return m_ht.equal_range(key); }
-    
-    
-    /**
-     * @copydoc equal_range(const K& key)
-     * 
-     * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
-     * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    std::pair<iterator, iterator> equal_range(const K& key, std::size_t precalculated_hash) { 
-        return m_ht.equal_range(key, precalculated_hash); 
-    }
-    
-    /**
-     * @copydoc equal_range(const K& key)
-     */
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    std::pair<const_iterator, const_iterator> equal_range(const K& key) const { return m_ht.equal_range(key); }
-    
-    /**
-     * @copydoc equal_range(const K& key, std::size_t precalculated_hash)
-     */    
-    template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> 
-    std::pair<const_iterator, const_iterator> equal_range(const K& key, std::size_t precalculated_hash) const { 
-        return m_ht.equal_range(key, precalculated_hash); 
-    }
-    
-    
-    
-    
-    /*
-     * Bucket interface 
-     */
-    size_type bucket_count() const { return m_ht.bucket_count(); }
-    size_type max_bucket_count() const { return m_ht.max_bucket_count(); }
-    
-    
-    /*
-     *  Hash policy 
-     */
-    float load_factor() const { return m_ht.load_factor(); }
-    float max_load_factor() const { return m_ht.max_load_factor(); }
-    void max_load_factor(float ml) { m_ht.max_load_factor(ml); }
-    
-    void rehash(size_type count_) { m_ht.rehash(count_); }
-    void reserve(size_type count_) { m_ht.reserve(count_); }
-    
-    
-    /*
-     * Observers
-     */
-    hasher hash_function() const { return m_ht.hash_function(); }
-    key_equal key_eq() const { return m_ht.key_eq(); }
-    
-    /*
-     * Other
-     */
-    
-    /**
-     * Convert a const_iterator to an iterator.
-     */
-    iterator mutable_iterator(const_iterator pos) {
-        return m_ht.mutable_iterator(pos);
-    }
-    
-    size_type overflow_size() const noexcept { return m_ht.overflow_size(); }
-    
-    friend bool operator==(const hopscotch_map& lhs, const hopscotch_map& rhs) {
-        if(lhs.size() != rhs.size()) {
-            return false;
-        }
-        
-        for(const auto& element_lhs : lhs) {
-            const auto it_element_rhs = rhs.find(element_lhs.first);
-            if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) {
-                return false;
-            }
-        }
-        
-        return true;
-    }
-
-    friend bool operator!=(const hopscotch_map& lhs, const hopscotch_map& rhs) {
-        return !operator==(lhs, rhs);
-    }
-
-    friend void swap(hopscotch_map& lhs, hopscotch_map& rhs) {
-        lhs.swap(rhs);
-    }
-
-
-    
-private:
-    ht m_ht;
-};
-
-
-/**
- * Same as `tsl::hopscotch_map<Key, T, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, tsl::hh::prime_growth_policy>`.
- */
-template<class Key, 
-         class T, 
-         class Hash = std::hash<Key>,
-         class KeyEqual = std::equal_to<Key>,
-         class Allocator = std::allocator<std::pair<Key, T>>,
-         unsigned int NeighborhoodSize = 62,
-         bool StoreHash = false>
-using hopscotch_pg_map = hopscotch_map<Key, T, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, tsl::hh::prime_growth_policy>;
-
-} // end namespace tsl
-
-#endif
diff --git a/modelling/src/pocket_finder.cc b/modelling/src/pocket_finder.cc
index f3f1b8f7c25884545604d0c208da114430b3954f..85d68e1ad5dd934a9819247a54cd68202313b052 100644
--- a/modelling/src/pocket_finder.cc
+++ b/modelling/src/pocket_finder.cc
@@ -13,18 +13,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <chrono>
-#include <tuple>
-#include <unordered_map>
-#include <limits>
 
 #include <promod3/modelling/pocket_finder.hh>
 #include <promod3/core/runtime_profiling.hh>
-#include <promod3/modelling/hopscotch_map.h>
+#include <promod3/modelling/robin_hood.h>
+#include <promod3/core/portable_binary_serializer.hh>
+#include <promod3/core/check_io.hh>
 
 #include <promod3/core/eigen_types.hh>
 #include <promod3/core/superpose.hh>
-#include <ost/mol/atom_view.hh>
 
 namespace {
   
@@ -38,12 +35,12 @@ struct PocketHasherKey {
   inline void SetX(int32_t x) { key = (key & (~x_mask)) | ((x+31) << 12); }
   inline void SetY(int32_t y) { key = (key & (~y_mask)) | ((y+31) << 6); }
   inline void SetZ(int32_t z) { key = (key & (~z_mask)) | (z+31); }
-  inline uint32_t GetA() { return (key & a_mask) >> 26; }
-  inline uint32_t GetB() { return (key & b_mask) >> 22; }
-  inline uint32_t GetC() { return (key & c_mask) >> 18; }
-  inline int32_t GetX() { return ((key & x_mask) >> 12) - 31; }
-  inline int32_t GetY() { return ((key & y_mask) >> 6) - 31; }
-  inline int32_t GetZ() { return (key & z_mask) - 31; }
+  inline uint32_t GetA() const { return (key & a_mask) >> 26; }
+  inline uint32_t GetB() const { return (key & b_mask) >> 22; }
+  inline uint32_t GetC() const { return (key & c_mask) >> 18; }
+  inline int32_t GetX() const { return ((key & x_mask) >> 12) - 31; }
+  inline int32_t GetY() const { return ((key & y_mask) >> 6) - 31; }
+  inline int32_t GetZ() const { return (key & z_mask) - 31; }
     
   bool operator==(const PocketHasherKey& other) const {
     return key == other.key; 
@@ -54,42 +51,68 @@ struct PocketHasherKey {
   }
 
   // data layout: 00aaaabbbbccccxxxxxxyyyyyyzzzzzz
-  // a,b,c can represent values in [0,15}
+  // a,b,c can represent values in [0,15]
   // x,y,z can represent values in [-31,31]
   // THERE ARE NO CHECKS FOR OVERFLOWS!
-  static const uint32_t a_mask = 1006632960;
-  static const uint32_t b_mask = 62914560;
-  static const uint32_t c_mask = 3932160;
-  static const uint32_t x_mask = 258048;
-  static const uint32_t y_mask = 4032;
-  static const uint32_t z_mask = 63;
+  static const uint32_t a_mask = 0x3C000000;
+  static const uint32_t b_mask = 0x03C00000;
+  static const uint32_t c_mask = 0x003C0000;
+  static const uint32_t x_mask = 0x0003F000;
+  static const uint32_t y_mask = 0x00000FC0;
+  static const uint32_t z_mask = 0x0000003F;
+
   uint32_t key;
 };
-  
+
 
 struct PocketHasherKeyHasher {
   std::size_t operator()(const PocketHasherKey& k) const {
-    std::hash<uint32_t> h;
-    return h(k.key); 
+    static robin_hood::hash<uint32_t> hasher;
+    return hasher(k.key);
   }      
 };
+
+
+struct PocketHasherValueItem {
+
+  PocketHasherValueItem(): data(0) { }
+
+  inline void SetQueryIdx(uint32_t idx) { data = (data & (~q_mask)) | (idx << 21); }
+  inline void SetTriangleIdx(uint32_t idx) { data = (data & (~t_mask)) | idx; }
+  inline uint32_t GetQueryIdx() const { return (data & q_mask) >> 21; }
+  inline uint32_t GetTriangleIdx() const { return data & t_mask; }
+
+  // data layout: qqqqqqqqqqqttttttttttttttttttttt
+  // query idx (q) can represent values in [0, 2047]
+  // triangle idx (t) can represent values in [0, 2097151]
+  // THERE ARE NO CHECKS FOR OVERFLOWS
+  static const uint32_t q_mask = 0xFFE00000;
+  static const uint32_t t_mask = 0x001FFFFF;
+  uint32_t data;
+};
+
   
+typedef std::vector<PocketHasherValueItem> PocketHasherValue;
+typedef robin_hood::unordered_map<PocketHasherKey, PocketHasherValue, 
+                                  PocketHasherKeyHasher> PocketHasherMap;
 
-typedef std::vector<std::pair<uint32_t, uint32_t> > PocketHasherValue;
-typedef tsl::hopscotch_map<PocketHasherKey, PocketHasherValue, 
-                           PocketHasherKeyHasher> PocketHasherMap;
   
 
-void Transform(const promod3::core::EMat3X& pos, int p1, int p2, int p3, 
-               promod3::core::EMat3X& transformed_pos) {
+void BaseTransform(const promod3::core::EMat3X& pos, int p1, int p2, int p3, 
+                   promod3::core::EMat3X& transformed_pos) {
+
+  // translate to new origin
+  transformed_pos = pos.colwise() - pos.col(p1);
+
+  // define new coordinate system 
   promod3::core::EMat3 base;
-  base.col(0) = pos.col(p2) - pos.col(p1);
-  base.col(1) = pos.col(p3) - pos.col(p1);
-  base.col(2) = base.col(0).cross(base.col(1));
-  base.col(0).normalize();
-  base.col(1).normalize();
-  base.col(2).normalize();
-  transformed_pos = base.inverse() * pos;
+  base.col(0) = transformed_pos.col(p2);
+  base.col(2) = base.col(0).cross(transformed_pos.col(p3));
+  base.col(1) = base.col(0).cross(base.col(2));
+  base.colwise().normalize();
+
+  // change base
+  transformed_pos = base.inverse() * transformed_pos;
 }
 
 
@@ -112,9 +135,180 @@ void SetupEigenMatrices(const geom::Vec3List& positions,
     }
   }
 }
-} // anon ns
 
 
+bool RefineInitialHit(const promod3::modelling::PocketQuery& query,  
+                      const promod3::core::EMat3X& target_pos, Real dist_thresh,
+                      int query_idx, int query_triangle_idx, int target_p1, 
+                      int target_p2, int target_p3, geom::Mat4& mat, 
+                      std::vector<std::pair<int, int> >& alignment) {
+
+  // the query is only available as geom::Vec3List. This makes sense from a 
+  // usability perspective, but we transfer the thing to an Eigen matrix
+  // for processing here...
+  int query_n = query.GetQuerySize(query_idx);
+  const geom::Vec3List& query_vec3_pos = query.GetPositions(query_idx);
+  promod3::core::EMat3X query_pos = promod3::core::EMat3X::Zero(3, query_n);
+  for(int i = 0; i < query_n; ++i) {
+    promod3::core::EMatFillCol(query_pos, i, query_vec3_pos[i]);
+  }
+
+  // we superpose iteratively, so we need to keep track of several 
+  // transformations
+  std::vector<geom::Mat4> transformations;
+
+  // the initial alignment is defined by the triangles in query and target
+  int temp = query_triangle_idx;
+  int query_p1 = static_cast<int>(temp/(query_n*query_n));
+  temp -= query_p1*query_n*query_n;
+  int query_p2 = static_cast<int>(temp/query_n);
+  temp -= query_p2*query_n;
+  int query_p3 = temp;
+
+  alignment.clear();
+  alignment.push_back(std::make_pair(query_p1, target_p1));
+  alignment.push_back(std::make_pair(query_p2, target_p2));
+  alignment.push_back(std::make_pair(query_p3, target_p3));
+
+  // to save some square roots, we're only dealing with squared distances below
+  Real squared_dist_thresh = dist_thresh * dist_thresh;
+
+  for(int iteration = 0; iteration < 5; ++iteration) {
+
+    if(alignment.size() < 3) {
+      return false;
+    }
+
+    promod3::core::EMatX3 m1 = promod3::core::EMatX3::Zero(alignment.size(),3); 
+    promod3::core::EMatX3 m2 = promod3::core::EMatX3::Zero(alignment.size(),3); 
+
+    for(uint i = 0; i < alignment.size(); ++i) {
+      m1.row(i) = query_pos.col(alignment[i].first).transpose();
+      m2.row(i) = target_pos.col(alignment[i].second).transpose();
+    }
+
+    geom::Mat4 t = promod3::core::MinRMSDSuperposition(m1, m2);
+
+    // apply the transform to the query positions
+    promod3::core::EMat3 rot;
+    rot(0,0) = t(0,0); rot(0,1) = t(0,1); rot(0,2) = t(0,2);
+    rot(1,0) = t(1,0); rot(1,1) = t(1,1); rot(1,2) = t(1,2);
+    rot(2,0) = t(2,0); rot(2,1) = t(2,1); rot(2,2) = t(2,2);
+  
+    promod3::core::EVec3 trans;
+    trans(0,0) = t(0,3);
+    trans(1,0) = t(1,3);
+    trans(2,0) = t(2,3);
+
+    query_pos = rot * query_pos;
+    query_pos = query_pos.colwise() + trans;
+
+    std::vector<std::pair<int,int> > new_alignment;
+
+    for(int i = 0; i < query_pos.cols(); ++i) {
+      promod3::core::ERVecX squared_distances = 
+      (target_pos.colwise() - query_pos.col(i)).colwise().squaredNorm();
+      Real min = squared_distances.minCoeff();
+      if(min <= squared_dist_thresh) {
+        int min_idx = -1;
+        for(int j = 0; j < squared_distances.cols(); ++j) {
+          if(squared_distances(0,j) == min) {
+            min_idx = j;
+            break;
+          }
+        }
+        new_alignment.push_back(std::make_pair(i, min_idx));
+      }
+    }
+
+    transformations.push_back(t);
+    if(new_alignment == alignment) {
+      break;
+    } 
+    alignment = new_alignment;
+  }
+
+  // chain together the final transformation matrix
+  int t_idx = transformations.size() - 1;
+  mat = transformations[t_idx];
+  while(t_idx > 0) {
+    mat = mat * transformations[--t_idx];
+  }
+
+  return true;
+}
+
+
+void ProcessAccumulator(const promod3::modelling::PocketQuery& query,
+                        const promod3::core::EMat3X& target_pos, 
+                        Real coverage_thresh, Real dist_thresh,
+                        int target_p1, int target_p2, int target_p3,
+                        promod3::core::EMatXX& accumulator,
+                        std::vector<promod3::modelling::PocketMatch>& results) {
+
+  promod3::core::ERVecX w = accumulator.colwise().maxCoeff();
+  for(int query_idx = 0; query_idx < accumulator.cols(); ++query_idx) {
+    if(w(0,query_idx) / (query.GetQuerySize(query_idx)-3) >= coverage_thresh) {
+      int query_size = query.GetQuerySize(query_idx);
+      int query_n_triangles = query_size*query_size*query_size;
+      for(int query_triangle_idx = 0; 
+          query_triangle_idx < query_n_triangles; ++query_triangle_idx) {
+        if(accumulator(query_triangle_idx, query_idx) / (query_size-3) >= 
+           coverage_thresh) {
+          geom::Mat4 mat;
+          std::vector<std::pair<int,int> > aln;
+          if(RefineInitialHit(query, target_pos, 1.0, query_idx, 
+                              query_triangle_idx, target_p1, 
+                              target_p2, target_p3, mat, aln)) {
+          
+            // only add the result if its unique
+            bool already_there = false;
+            for(uint res_idx = 0; res_idx < results.size(); ++res_idx) {
+              // we do not check for the transformation matrix
+              // if the alignment is the same, the matrix will also be the same
+              if(results[res_idx].query_idx == query_idx &&
+                 results[res_idx].aln == aln) {
+                already_there = true;
+                break;
+              } 
+            }
+            if(!already_there) {
+              results.push_back(promod3::modelling::PocketMatch(query_idx, 
+                                                                mat, aln));
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // done, let's prepare the accumulator for another round of counting
+  accumulator.setZero();
+}
+
+
+template<typename T>
+void WriteVec(std::ofstream& stream, const std::vector<T>& vec) {
+  uint32_t size = vec.size();
+  stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  if(size > 0) {
+    stream.write(reinterpret_cast<const char*>(&vec[0]), size*sizeof(T));
+  }
+}
+
+
+template<typename T>
+void ReadVec(std::ifstream& stream, std::vector<T>& vec) {
+  uint32_t size;
+  stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  if(size > 0) {
+    vec.resize(size);
+    stream.read(reinterpret_cast<char*>(&vec[0]), size*sizeof(T));
+  }
+}
+
+} // anon ns
+
 
 namespace promod3 { namespace modelling {
 
@@ -128,9 +322,22 @@ struct PocketQueryData {
 };
 
 
+PocketQuery::PocketQuery() { }
+
+
 PocketQuery::PocketQuery(const geom::Vec3List& positions, 
                          const String& identifier) {
 
+  if(positions.size() < 4) {
+    throw promod3::Error("Require at least 4 positions to construct "
+                         "PocketQuery");
+  }
+
+  if(positions.size() > 128) {
+    throw promod3::Error("PocketQuery is limited to a maximum of 128 "
+                         "positions");
+  }
+
   data_ = std::shared_ptr<PocketQueryData>(new PocketQueryData);
   data_->positions.push_back(positions);
   data_->identifiers.push_back(identifier);
@@ -148,7 +355,6 @@ PocketQuery::PocketQuery(const geom::Vec3List& positions,
   int n = eigen_positions.cols();
 
   for(int p1 = 0; p1 < n; ++p1) {
-    std::cerr<<p1<<std::endl;
     for(int p2 = 0; p2 < n; ++p2) {
       if(p2 == p1) {
         continue;
@@ -172,7 +378,7 @@ PocketQuery::PocketQuery(const geom::Vec3List& positions,
         // transform all positions into the vector basis defined by the 
         // triangle with edges at p1, p2, p3 and add the stuff to the hash 
         // map
-        Transform(eigen_positions, p1, p2, p3, transformed_pos);
+        BaseTransform(eigen_positions, p1, p2, p3, transformed_pos);
         for(int i = 0; i < n; ++i) {
           if(i != p1 && i != p2 && i != p3) {
             d = static_cast<int32_t>(transformed_pos(0,i));
@@ -190,9 +396,10 @@ PocketQuery::PocketQuery(const geom::Vec3List& positions,
                 data_->map[key] = PocketHasherValue();
                 it = data_->map.find(key);
               }
-              it.value().push_back(std::make_pair(triangle_idx, query_idx));
-              // for std::unordered_map you have to do:
-              //it->second.push_back(std::make_pair(triangle_idx, query_idx));
+              PocketHasherValueItem item;
+              item.SetQueryIdx(query_idx);
+              item.SetTriangleIdx(triangle_idx);
+              it->second.push_back(item);
             }
           }
         }
@@ -213,6 +420,11 @@ PocketQuery::PocketQuery(const std::vector<PocketQuery>& queries) {
   // iterate over all queries and update data_ along the way
   for(auto query_it = queries.begin(); query_it != queries.end(); ++query_it) {
 
+    if(data_->n_queries + query_it->data_->n_queries > 2048) {
+      throw promod3::Error("Can at most combine 2048 PocketQueries to one "
+                           "single query");
+    }
+
     // update the map in data_
     for(auto other_map_it = query_it->data_->map.begin(); 
         other_map_it != query_it->data_->map.end(); ++other_map_it) {
@@ -221,14 +433,13 @@ PocketQuery::PocketQuery(const std::vector<PocketQuery>& queries) {
         map[other_map_it->first] = PocketHasherValue();
         map_it = map.find(other_map_it->first);
       }
-      for(auto value_it = other_map_it->second.begin(); 
-          value_it != other_map_it->second.end(); ++value_it) {
+      for(auto item_it = other_map_it->second.begin(); 
+          item_it != other_map_it->second.end(); ++item_it) {
         // we have to update the second element of the value pairs (query index)
-        std::pair<int,int> new_value = 
-        std::make_pair(value_it->first, value_it->second + data_->n_queries);
-        map_it.value().push_back(new_value);
-        // for std::unordered_map you have to do:
-        //map_it->second.push_back(new_value);
+        PocketHasherValueItem new_item;
+        new_item.SetQueryIdx(item_it->GetQueryIdx() + data_->n_queries);
+        new_item.SetTriangleIdx(item_it->GetTriangleIdx());
+        map_it->second.push_back(new_item);
       }
     }
 
@@ -245,6 +456,123 @@ PocketQuery::PocketQuery(const std::vector<PocketQuery>& queries) {
 }
 
 
+PocketQuery PocketQuery::Load(const String& filename) {
+  
+  std::ifstream in_stream_(filename.c_str(), std::ios::binary);
+  if (!in_stream_) {
+    std::stringstream ss;
+    ss << "The file '" << filename << "' does not exist.";
+    throw promod3::Error(ss.str());
+  } 
+
+  core::PortableBinaryDataSource in_stream(in_stream_);
+  core::CheckMagicNumber(in_stream);
+  uint32_t version = core::GetVersionNumber(in_stream_);
+  if(version != 1) {
+    std::stringstream ss;
+    ss << "Unsupported file version '" << version << "' in '" << filename;
+    throw promod3::Error(ss.str());
+  }
+  core::CheckTypeSize<uint32_t>(in_stream);
+  core::CheckTypeSize<float>(in_stream);
+
+  PocketQuery query;
+  query.data_ = std::shared_ptr<PocketQueryData>(new PocketQueryData);
+  std::vector<uint32_t> query_sizes;
+  std::vector<float> position_vec;
+  std::vector<uint32_t> keys;
+  std::vector<uint32_t> n_items;
+  std::vector<uint32_t> items;
+
+  in_stream & query.data_->identifiers;
+  ReadVec(in_stream.Stream(), query_sizes);
+  ReadVec(in_stream.Stream(), position_vec);
+  ReadVec(in_stream.Stream(), keys);
+  ReadVec(in_stream.Stream(), n_items);
+  ReadVec(in_stream.Stream(), items);
+
+  query.data_->n_queries = query.data_->identifiers.size();
+  query.data_->n_max = 0;
+  int current_pos = 0;
+  for(int i = 0; i < query.data_->n_queries; ++i) {
+    geom::Vec3List positions;
+    for(uint j = 0; j < query_sizes[i]; ++j) {
+      positions.push_back(geom::Vec3(position_vec[current_pos],
+                                     position_vec[current_pos+1],
+                                     position_vec[current_pos+2]));
+      current_pos += 3;
+    }
+    query.data_->positions.push_back(positions);
+    query.data_->n_max = std::max(query.data_->n_max, 
+                                  static_cast<int>(query_sizes[i]));
+  }
+
+  current_pos = 0;
+  for(uint i = 0; i < keys.size(); ++i) {
+    PocketHasherKey key;
+    key.key = keys[i];
+    PocketHasherValue value;
+    for(uint j = 0; j < n_items[i]; ++j) {
+      PocketHasherValueItem item;
+      item.data = items[current_pos++];
+      value.push_back(item);
+    }
+    query.data_->map[key] = value;
+  }
+
+  return query;
+}
+
+
+void PocketQuery::Save(const String& filename) const {
+
+  std::ofstream out_stream_(filename.c_str(), std::ios::binary);
+  if (!out_stream_) {
+    std::stringstream ss;
+    ss << "The file '" << filename << "' cannot be opened.";
+    throw promod3::Error(ss.str());
+  }
+
+  std::vector<uint32_t> query_sizes;
+  std::vector<float> position_vec;
+  std::vector<uint32_t> keys;
+  std::vector<uint32_t> n_items;
+  std::vector<uint32_t> items;
+
+  for(uint i = 0; i < data_->positions.size(); ++i) {
+    int n = data_->positions[i].size();
+    query_sizes.push_back(n);
+    for(int j = 0; j < n; ++j) {
+      position_vec.push_back(data_->positions[i][j][0]);
+      position_vec.push_back(data_->positions[i][j][1]);
+      position_vec.push_back(data_->positions[i][j][2]);
+    }
+  }
+
+  for(PocketHasherMap::const_iterator it = data_->map.begin();
+      it != data_->map.end(); ++it) {
+    keys.push_back(it->first.key);
+    n_items.push_back(it->second.size());
+    for(uint i = 0; i < n_items.back(); ++i) {
+      items.push_back(it->second[i].data);
+    }
+  }
+
+  core::PortableBinaryDataSink out_stream(out_stream_);
+  core::WriteMagicNumber(out_stream);
+  core::WriteVersionNumber(out_stream, 1);
+  core::WriteTypeSize<uint32_t>(out_stream);
+  core::WriteTypeSize<float>(out_stream);
+
+  out_stream & data_->identifiers;
+  WriteVec(out_stream.Stream(), query_sizes);
+  WriteVec(out_stream.Stream(), position_vec);
+  WriteVec(out_stream.Stream(), keys);
+  WriteVec(out_stream.Stream(), n_items);
+  WriteVec(out_stream.Stream(), items);
+}
+
+
 const geom::Vec3List& PocketQuery::GetPositions(uint idx) const{
   return data_->positions[idx];
 }
@@ -260,8 +588,17 @@ size_t PocketQuery::GetN() const {
 } 
 
 
-geom::Mat4 FindTransform(const PocketQuery& query,
-                         const geom::Vec3List& positions) {
+size_t PocketQuery::GetQuerySize(uint idx) const {
+  return data_->positions[idx].size();
+}
+
+
+std::vector<PocketMatch> FindPockets(const PocketQuery& query, 
+                                     const geom::Vec3List& positions,
+                                     Real coverage_thresh, 
+                                     Real distance_thresh) {
+
+  std::vector<PocketMatch> results;
 
   promod3::core::EMat3X eigen_positions;
   promod3::core::EMatXX eigen_distances;
@@ -275,25 +612,17 @@ geom::Mat4 FindTransform(const PocketQuery& query,
 
   // fetch hash map and setup accumulator
   const PocketHasherMap& map = query.data_->map;
-  int n_max = query.data_->n_max;
-  int n_queries = query.data_->n_queries;
   promod3::core::EMatXX accumulator = 
-  promod3::core::EMatXX::Zero(n_max*n_max*n_max, n_queries);
+  promod3::core::EMatXX::Zero(std::pow(query.data_->n_max, 3), 
+                              query.data_->n_queries);
     
   for(int p1 = 0; p1 < n_target; ++p1) {
-    std::cerr<<p1<<std::endl;
-    for(int p2 = 0; p2 < n_target; ++p2) {
-      if(p2 == p1) {
-        continue;
-      }
+    for(int p2 = p1+1; p2 < n_target; ++p2) {
       a = static_cast<uint32_t>(eigen_distances(p1,p2));
       if(a > 12) {
         continue;
       }
-      for(int p3 = 0; p3 < n_target; ++p3) {
-        if(p3 == p1 || p3 == p2) {
-          continue;
-        }
+      for(int p3 = p2+1; p3 < n_target; ++p3) {
         b = static_cast<uint32_t>(eigen_distances(p1,p3));
         c = static_cast<uint32_t>(eigen_distances(p2,p3));
         if(b > 12 || c > 12) {
@@ -306,7 +635,7 @@ geom::Mat4 FindTransform(const PocketQuery& query,
         // triangle with edges at p1, p2, p3 
         // search for occurences of that triangle in combination with the
         // transformed positions in the hash map
-        Transform(eigen_positions, p1, p2, p3, transformed_pos);
+        BaseTransform(eigen_positions, p1, p2, p3, transformed_pos);
         for(int i = 0; i < n_target; ++i) {
           if(i != p1 && i != p2 && i != p3) {
             d = static_cast<int32_t>(transformed_pos(0,i));
@@ -320,37 +649,21 @@ geom::Mat4 FindTransform(const PocketQuery& query,
               if(it != map.end()) {
                 for(PocketHasherValue::const_iterator v_it = it->second.begin(); 
                     v_it != it->second.end(); ++v_it) {
-                  accumulator(v_it->first, v_it->second) += 1.0;
+                  accumulator(v_it->GetTriangleIdx(), v_it->GetQueryIdx()) += 1.0;
                 }
               }
             }
           }
         }
-            
-        // search for high vote numbers in accumulator
-        Eigen::Matrix<Real,1,1> w = accumulator.colwise().maxCoeff();
-        for(int i = 0; i < accumulator.cols(); ++i) {
-          if(w(0,i) > 10) {
-            //TODO do something awesome with the found hit
-            std::cerr<<"asdfsadfasdfasdf"<<std::endl;
-          }
-        }
-        accumulator.setZero();
+        // modifies results based on counts in the accumulator and
+        // wipes the accumulator for another round of counting
+        ProcessAccumulator(query, eigen_positions, coverage_thresh, 1.0, 
+                           p1, p2, p3, accumulator, results);
       }
     }
   }
 
-  return geom::Mat4();
-}
-
-
-void PocketFinder(const PocketQuery& query,
-                  const geom::Vec3List& target_positions) {
-
-
-  geom::Mat4 t = FindTransform(query, target_positions);
-
-  std::cerr<<t<<std::endl;
+  return results;
 }
 
 }} // ns
diff --git a/modelling/src/pocket_finder.hh b/modelling/src/pocket_finder.hh
index 5da95d2c96a8263495f36d0963f8769c3248680e..f9884466d6fdefa0f54d262b800bc76bf0b64d73 100644
--- a/modelling/src/pocket_finder.hh
+++ b/modelling/src/pocket_finder.hh
@@ -32,17 +32,41 @@ struct PocketQuery{
 
   PocketQuery(const std::vector<PocketQuery>& queries);
 
+  static PocketQuery Load(const String& filename);
+
+  void Save(const String& filename) const;
+
   const geom::Vec3List& GetPositions(uint idx) const;
 
   const std::vector<String>& GetIdentifiers() const;
 
   size_t GetN() const;
 
+  size_t GetQuerySize(uint idx) const;
+
   std::shared_ptr<PocketQueryData> data_;
+
+private:
+
+  PocketQuery();
 };
 
-void PocketFinder(const PocketQuery& query,
-                  const geom::Vec3List& target_positions);
+
+struct PocketMatch{
+
+  PocketMatch(int q_idx, const geom::Mat4& m, 
+              const std::vector<std::pair<int,int> >& a): query_idx(q_idx),
+                                                          mat(m), aln(a) { }
+  int query_idx;
+  geom::Mat4 mat;
+  std::vector<std::pair<int,int> > aln;
+};
+
+
+std::vector<PocketMatch> FindPockets(const PocketQuery& query,
+                                     const geom::Vec3List& positions,
+                                     Real coverage_thresh = 0.5,
+                                     Real distance_thresh = 1.0);
 
 
 }} // ns
diff --git a/modelling/src/robin_hood.h b/modelling/src/robin_hood.h
new file mode 100644
index 0000000000000000000000000000000000000000..fe02f6b7b1e613d45654cc1f175741835c4f3fc9
--- /dev/null
+++ b/modelling/src/robin_hood.h
@@ -0,0 +1,2063 @@
+/////////////////////////////////////////////////////////////////
+// SOURCE: https://github.com/martinus/robin-hood-hashing.git  //
+// COMMIT: c9d72bdf02c4289903544325b272eb9f4adb7b83            //
+// WE ASSUME THAT NO UPDATES ARE REQUIRED                      //
+/////////////////////////////////////////////////////////////////
+
+
+//                 ______  _____                 ______                _________
+//  ______________ ___  /_ ___(_)_______         ___  /_ ______ ______ ______  /
+//  __  ___/_  __ \__  __ \__  / __  __ \        __  __ \_  __ \_  __ \_  __  /
+//  _  /    / /_/ /_  /_/ /_  /  _  / / /        _  / / // /_/ // /_/ // /_/ /
+//  /_/     \____/ /_.___/ /_/   /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/
+//                                      _/_____/
+//
+// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20
+// version 3.4.1
+// https://github.com/martinus/robin-hood-hashing
+//
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2018-2019 Martin Ankerl <http://martin.ankerl.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef ROBIN_HOOD_H_INCLUDED
+#define ROBIN_HOOD_H_INCLUDED
+
+// see https://semver.org/
+#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes
+#define ROBIN_HOOD_VERSION_MINOR 4 // for adding functionality in a backwards-compatible manner
+#define ROBIN_HOOD_VERSION_PATCH 1 // for backwards-compatible bug fixes
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+// #define ROBIN_HOOD_LOG_ENABLED
+#ifdef ROBIN_HOOD_LOG_ENABLED
+#    include <iostream>
+#    define ROBIN_HOOD_LOG(x) std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl
+#else
+#    define ROBIN_HOOD_LOG(x)
+#endif
+
+// #define ROBIN_HOOD_TRACE_ENABLED
+#ifdef ROBIN_HOOD_TRACE_ENABLED
+#    include <iostream>
+#    define ROBIN_HOOD_TRACE(x) \
+        std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl
+#else
+#    define ROBIN_HOOD_TRACE(x)
+#endif
+
+// all non-argument macros should use this facility. See
+// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/
+#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x()
+
+// mark unused members with this macro
+#define ROBIN_HOOD_UNUSED(identifier)
+
+// bitness
+#if SIZE_MAX == UINT32_MAX
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32
+#elif SIZE_MAX == UINT64_MAX
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64
+#else
+#    error Unsupported bitness
+#endif
+
+// endianess
+#ifdef _WIN32
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0
+#else
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \
+        (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#endif
+
+// inline
+#ifdef _WIN32
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline)
+#else
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline))
+#endif
+
+// exceptions
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0
+#else
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1
+#endif
+
+// count leading/trailing bits
+#ifdef _WIN32
+#    if ROBIN_HOOD(BITNESS) == 32
+#        define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward
+#    else
+#        define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64
+#    endif
+#    include <intrin.h>
+#    pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD))
+#    define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x)                                       \
+        [](size_t mask) noexcept->int {                                               \
+            unsigned long index;                                                      \
+            return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast<int>(index) \
+                                                            : ROBIN_HOOD(BITNESS);    \
+        }                                                                             \
+        (x)
+#else
+#    if ROBIN_HOOD(BITNESS) == 32
+#        define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl
+#        define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl
+#    else
+#        define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll
+#        define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll
+#    endif
+#    define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS))
+#    define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS))
+#endif
+
+// fallthrough
+#ifndef __has_cpp_attribute // For backwards compatibility
+#    define __has_cpp_attribute(x) 0
+#endif
+#if __has_cpp_attribute(clang::fallthrough)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]]
+#elif __has_cpp_attribute(gnu::fallthrough)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]]
+#else
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH()
+#endif
+
+// likely/unlikely
+#if defined(_WIN32)
+#    define ROBIN_HOOD_LIKELY(condition) condition
+#    define ROBIN_HOOD_UNLIKELY(condition) condition
+#else
+#    define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1)
+#    define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0)
+#endif
+
+// workaround missing "is_trivially_copyable" in g++ < 5.0
+// See https://stackoverflow.com/a/31798726/48181
+#if defined(__GNUC__) && __GNUC__ < 5
+#    define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
+#else
+#    define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
+#endif
+
+// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]]
+#else
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD()
+#endif
+
+namespace robin_hood {
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+#    define ROBIN_HOOD_STD std
+#else
+
+// c++11 compatibility layer
+namespace ROBIN_HOOD_STD {
+template <class T>
+struct alignment_of
+    : std::integral_constant<std::size_t, alignof(typename std::remove_all_extents<T>::type)> {};
+
+template <class T, T... Ints>
+class integer_sequence {
+public:
+    using value_type = T;
+    static_assert(std::is_integral<value_type>::value, "not integral type");
+    static constexpr std::size_t size() noexcept {
+        return sizeof...(Ints);
+    }
+};
+template <std::size_t... Inds>
+using index_sequence = integer_sequence<std::size_t, Inds...>;
+
+namespace detail_ {
+template <class T, T Begin, T End, bool>
+struct IntSeqImpl {
+    using TValue = T;
+    static_assert(std::is_integral<TValue>::value, "not integral type");
+    static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)");
+
+    template <class, class>
+    struct IntSeqCombiner;
+
+    template <TValue... Inds0, TValue... Inds1>
+    struct IntSeqCombiner<integer_sequence<TValue, Inds0...>, integer_sequence<TValue, Inds1...>> {
+        using TResult = integer_sequence<TValue, Inds0..., Inds1...>;
+    };
+
+    using TResult =
+        typename IntSeqCombiner<typename IntSeqImpl<TValue, Begin, Begin + (End - Begin) / 2,
+                                                    (End - Begin) / 2 == 1>::TResult,
+                                typename IntSeqImpl<TValue, Begin + (End - Begin) / 2, End,
+                                                    (End - Begin + 1) / 2 == 1>::TResult>::TResult;
+};
+
+template <class T, T Begin>
+struct IntSeqImpl<T, Begin, Begin, false> {
+    using TValue = T;
+    static_assert(std::is_integral<TValue>::value, "not integral type");
+    static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+    using TResult = integer_sequence<TValue>;
+};
+
+template <class T, T Begin, T End>
+struct IntSeqImpl<T, Begin, End, true> {
+    using TValue = T;
+    static_assert(std::is_integral<TValue>::value, "not integral type");
+    static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+    using TResult = integer_sequence<TValue, Begin>;
+};
+} // namespace detail_
+
+template <class T, T N>
+using make_integer_sequence = typename detail_::IntSeqImpl<T, 0, N, (N - 0) == 1>::TResult;
+
+template <std::size_t N>
+using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+template <class... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+} // namespace ROBIN_HOOD_STD
+
+#endif
+
+namespace detail {
+
+// umul
+#if defined(__SIZEOF_INT128__)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1
+#    if defined(__GNUC__) || defined(__clang__)
+#        pragma GCC diagnostic push
+#        pragma GCC diagnostic ignored "-Wpedantic"
+using uint128_t = unsigned __int128;
+#        pragma GCC diagnostic pop
+#    endif
+inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept {
+    auto result = static_cast<uint128_t>(a) * static_cast<uint128_t>(b);
+    *high = static_cast<uint64_t>(result >> 64U);
+    return static_cast<uint64_t>(result);
+}
+#elif (defined(_WIN32) && ROBIN_HOOD(BITNESS) == 64)
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1
+#    include <intrin.h> // for __umulh
+#    pragma intrinsic(__umulh)
+#    ifndef _M_ARM64
+#        pragma intrinsic(_umul128)
+#    endif
+inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept {
+#    ifdef _M_ARM64
+    *high = __umulh(a, b);
+    return ((uint64_t)(a)) * (b);
+#    else
+    return _umul128(a, b, high);
+#    endif
+}
+#else
+#    define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 0
+#endif
+
+// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to
+// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with
+// care!
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept {
+    return reinterpret_cast<T>(ptr);
+}
+
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept {
+    return reinterpret_cast<T>(ptr);
+}
+
+// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
+// inlinings more difficult. Throws are also generally the slow path.
+template <typename E, typename... Args>
+ROBIN_HOOD(NOINLINE)
+#if ROBIN_HOOD(HAS_EXCEPTIONS)
+void doThrow(Args&&... args) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+    throw E(std::forward<Args>(args)...);
+}
+#else
+void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
+    abort();
+}
+#endif
+
+template <typename E, typename T, typename... Args>
+T* assertNotNull(T* t, Args&&... args) {
+    if (ROBIN_HOOD_UNLIKELY(nullptr == t)) {
+        doThrow<E>(std::forward<Args>(args)...);
+    }
+    return t;
+}
+
+template <typename T>
+inline T unaligned_load(void const* ptr) noexcept {
+    // using memcpy so we don't get into unaligned load problems.
+    // compiler should optimize this very well anyways.
+    T t;
+    std::memcpy(&t, ptr, sizeof(T));
+    return t;
+}
+
+// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor,
+// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a
+// pointer.
+template <typename T, size_t MinNumAllocs = 4, size_t MaxNumAllocs = 256>
+class BulkPoolAllocator {
+public:
+    BulkPoolAllocator() noexcept = default;
+
+    // does not copy anything, just creates a new allocator.
+    BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept
+        : mHead(nullptr)
+        , mListForFree(nullptr) {}
+
+    BulkPoolAllocator(BulkPoolAllocator&& o) noexcept
+        : mHead(o.mHead)
+        , mListForFree(o.mListForFree) {
+        o.mListForFree = nullptr;
+        o.mHead = nullptr;
+    }
+
+    BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept {
+        reset();
+        mHead = o.mHead;
+        mListForFree = o.mListForFree;
+        o.mListForFree = nullptr;
+        o.mHead = nullptr;
+        return *this;
+    }
+
+    BulkPoolAllocator&
+    operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept {
+        // does not do anything
+        return *this;
+    }
+
+    ~BulkPoolAllocator() noexcept {
+        reset();
+    }
+
+    // Deallocates all allocated memory.
+    void reset() noexcept {
+        while (mListForFree) {
+            T* tmp = *mListForFree;
+            free(mListForFree);
+            mListForFree = reinterpret_cast_no_cast_align_warning<T**>(tmp);
+        }
+        mHead = nullptr;
+    }
+
+    // allocates, but does NOT initialize. Use in-place new constructor, e.g.
+    //   T* obj = pool.allocate();
+    //   ::new (static_cast<void*>(obj)) T();
+    T* allocate() {
+        T* tmp = mHead;
+        if (!tmp) {
+            tmp = performAllocation();
+        }
+
+        mHead = *reinterpret_cast_no_cast_align_warning<T**>(tmp);
+        return tmp;
+    }
+
+    // does not actually deallocate but puts it in store.
+    // make sure you have already called the destructor! e.g. with
+    //  obj->~T();
+    //  pool.deallocate(obj);
+    void deallocate(T* obj) noexcept {
+        *reinterpret_cast_no_cast_align_warning<T**>(obj) = mHead;
+        mHead = obj;
+    }
+
+    // Adds an already allocated block of memory to the allocator. This allocator is from now on
+    // responsible for freeing the data (with free()). If the provided data is not large enough to
+    // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor.
+    void addOrFree(void* ptr, const size_t numBytes) noexcept {
+        // calculate number of available elements in ptr
+        if (numBytes < ALIGNMENT + ALIGNED_SIZE) {
+            // not enough data for at least one element. Free and return.
+            free(ptr);
+        } else {
+            add(ptr, numBytes);
+        }
+    }
+
+    void swap(BulkPoolAllocator<T, MinNumAllocs, MaxNumAllocs>& other) noexcept {
+        using std::swap;
+        swap(mHead, other.mHead);
+        swap(mListForFree, other.mListForFree);
+    }
+
+private:
+    // iterates the list of allocated memory to calculate how many to alloc next.
+    // Recalculating this each time saves us a size_t member.
+    // This ignores the fact that memory blocks might have been added manually with addOrFree. In
+    // practice, this should not matter much.
+    ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept {
+        auto tmp = mListForFree;
+        size_t numAllocs = MinNumAllocs;
+
+        while (numAllocs * 2 <= MaxNumAllocs && tmp) {
+            auto x = reinterpret_cast<T***>(tmp);
+            tmp = *x;
+            numAllocs *= 2;
+        }
+
+        return numAllocs;
+    }
+
+    // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree().
+    void add(void* ptr, const size_t numBytes) noexcept {
+        const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE;
+
+        auto data = reinterpret_cast<T**>(ptr);
+
+        // link free list
+        auto x = reinterpret_cast<T***>(data);
+        *x = mListForFree;
+        mListForFree = data;
+
+        // create linked list for newly allocated data
+        auto const headT =
+            reinterpret_cast_no_cast_align_warning<T*>(reinterpret_cast<char*>(ptr) + ALIGNMENT);
+
+        auto const head = reinterpret_cast<char*>(headT);
+
+        // Visual Studio compiler automatically unrolls this loop, which is pretty cool
+        for (size_t i = 0; i < numElements; ++i) {
+            *reinterpret_cast_no_cast_align_warning<char**>(head + i * ALIGNED_SIZE) =
+                head + (i + 1) * ALIGNED_SIZE;
+        }
+
+        // last one points to 0
+        *reinterpret_cast_no_cast_align_warning<T**>(head + (numElements - 1) * ALIGNED_SIZE) =
+            mHead;
+        mHead = headT;
+    }
+
+    // Called when no memory is available (mHead == 0).
+    // Don't inline this slow path.
+    ROBIN_HOOD(NOINLINE) T* performAllocation() {
+        size_t const numElementsToAlloc = calcNumElementsToAlloc();
+
+        // alloc new memory: [prev |T, T, ... T]
+        // std::cout << (sizeof(T*) + ALIGNED_SIZE * numElementsToAlloc) << " bytes" << std::endl;
+        size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc;
+        add(assertNotNull<std::bad_alloc>(malloc(bytes)), bytes);
+        return mHead;
+    }
+
+    // enforce byte alignment of the T's
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+    static constexpr size_t ALIGNMENT =
+        (std::max)(std::alignment_of<T>::value, std::alignment_of<T*>::value);
+#else
+    static const size_t ALIGNMENT =
+        (ROBIN_HOOD_STD::alignment_of<T>::value > ROBIN_HOOD_STD::alignment_of<T*>::value)
+            ? ROBIN_HOOD_STD::alignment_of<T>::value
+            : +ROBIN_HOOD_STD::alignment_of<T*>::value; // the + is for walkarround
+#endif
+
+    static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT;
+
+    static_assert(MinNumAllocs >= 1, "MinNumAllocs");
+    static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs");
+    static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE");
+    static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod");
+    static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT");
+
+    T* mHead{nullptr};
+    T** mListForFree{nullptr};
+};
+
+template <typename T, size_t MinSize, size_t MaxSize, bool IsFlatMap>
+struct NodeAllocator;
+
+// dummy allocator that does nothing
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, true> {
+
+    // we are not using the data, so just free it.
+    void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept {
+        free(ptr);
+    }
+};
+
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, false> : public BulkPoolAllocator<T, MinSize, MaxSize> {};
+
+// dummy hash, unsed as mixer when robin_hood::hash is already used
+template <typename T>
+struct identity_hash {
+    constexpr size_t operator()(T const& obj) const noexcept {
+        return static_cast<size_t>(obj);
+    }
+};
+
+// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making
+// my own here.
+namespace swappable {
+using std::swap;
+template <typename T>
+struct nothrow {
+    static const bool value = noexcept(swap(std::declval<T&>(), std::declval<T&>()));
+};
+
+} // namespace swappable
+
+} // namespace detail
+
+struct is_transparent_tag {};
+
+// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable,
+// which means it would  not be allowed to be used in std::memcpy. This struct is copyable, which is
+// also tested.
+template <typename T1, typename T2>
+struct pair {
+    using first_type = T1;
+    using second_type = T2;
+
+    template <typename U1 = T1, typename U2 = T2,
+              typename = typename std::enable_if<std::is_default_constructible<U1>::value &&
+                                                 std::is_default_constructible<U2>::value>::type>
+    constexpr pair() noexcept(noexcept(U1()) && noexcept(U2()))
+        : first()
+        , second() {}
+
+    // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+    explicit constexpr pair(std::pair<T1, T2> const& o) noexcept(
+        noexcept(T1(std::declval<T1 const&>())) && noexcept(T2(std::declval<T2 const&>())))
+        : first(o.first)
+        , second(o.second) {}
+
+    // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+    explicit constexpr pair(std::pair<T1, T2>&& o) noexcept(
+        noexcept(T1(std::move(std::declval<T1&&>()))) &&
+        noexcept(T2(std::move(std::declval<T2&&>()))))
+        : first(std::move(o.first))
+        , second(std::move(o.second)) {}
+
+    constexpr pair(T1&& a, T2&& b) noexcept(noexcept(T1(std::move(std::declval<T1&&>()))) &&
+                                            noexcept(T2(std::move(std::declval<T2&&>()))))
+        : first(std::move(a))
+        , second(std::move(b)) {}
+
+    template <typename U1, typename U2>
+    constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward<U1>(std::declval<U1&&>()))) &&
+                                            noexcept(T2(std::forward<U2>(std::declval<U2&&>()))))
+        : first(std::forward<U1>(a))
+        , second(std::forward<U2>(b)) {}
+
+    template <typename... U1, typename... U2>
+    constexpr pair(
+        std::piecewise_construct_t /*unused*/, std::tuple<U1...> a,
+        std::tuple<U2...> b) noexcept(noexcept(pair(std::declval<std::tuple<U1...>&>(),
+                                                    std::declval<std::tuple<U2...>&>(),
+                                                    ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+                                                    ROBIN_HOOD_STD::index_sequence_for<U2...>())))
+        : pair(a, b, ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+               ROBIN_HOOD_STD::index_sequence_for<U2...>()) {}
+
+    // constructor called from the std::piecewise_construct_t ctor
+    template <typename... U1, size_t... I1, typename... U2, size_t... I2>
+    pair(std::tuple<U1...>& a, std::tuple<U2...>& b,
+         ROBIN_HOOD_STD::index_sequence<I1...> /*unused*/,
+         ROBIN_HOOD_STD::index_sequence<
+             I2...> /*unused*/) noexcept(noexcept(T1(std::
+                                                         forward<U1>(std::get<I1>(
+                                                             std::declval<
+                                                                 std::tuple<U1...>&>()))...)) &&
+                                         noexcept(T2(std::forward<U2>(
+                                             std::get<I2>(std::declval<std::tuple<U2...>&>()))...)))
+        : first(std::forward<U1>(std::get<I1>(a))...)
+        , second(std::forward<U2>(std::get<I2>(b))...) {
+        // make visual studio compiler happy about warning about unused a & b.
+        // Visual studio's pair implementation disables warning 4100.
+        (void)a;
+        (void)b;
+    }
+
+    ROBIN_HOOD(NODISCARD) first_type& getFirst() noexcept {
+        return first;
+    }
+    ROBIN_HOOD(NODISCARD) first_type const& getFirst() const noexcept {
+        return first;
+    }
+    ROBIN_HOOD(NODISCARD) second_type& getSecond() noexcept {
+        return second;
+    }
+    ROBIN_HOOD(NODISCARD) second_type const& getSecond() const noexcept {
+        return second;
+    }
+
+    void swap(pair<T1, T2>& o) noexcept((detail::swappable::nothrow<T1>::value) &&
+                                        (detail::swappable::nothrow<T2>::value)) {
+        using std::swap;
+        swap(first, o.first);
+        swap(second, o.second);
+    }
+
+    T1 first;  // NOLINT(misc-non-private-member-variables-in-classes)
+    T2 second; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+template <typename A, typename B>
+void swap(pair<A, B>& a, pair<A, B>& b) noexcept(
+    noexcept(std::declval<pair<A, B>&>().swap(std::declval<pair<A, B>&>()))) {
+    a.swap(b);
+}
+
+// Hash an arbitrary amount of bytes. This is basically Murmur2 hash without caring about big
+// endianness. TODO(martinus) add a fallback for very large strings?
+static size_t hash_bytes(void const* ptr, size_t const len) noexcept {
+    static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
+    static constexpr uint64_t seed = UINT64_C(0xe17a1465);
+    static constexpr unsigned int r = 47;
+
+    auto const data64 = static_cast<uint64_t const*>(ptr);
+    uint64_t h = seed ^ (len * m);
+
+    size_t const n_blocks = len / 8;
+    for (size_t i = 0; i < n_blocks; ++i) {
+        auto k = detail::unaligned_load<uint64_t>(data64 + i);
+
+        k *= m;
+        k ^= k >> r;
+        k *= m;
+
+        h ^= k;
+        h *= m;
+    }
+
+    auto const data8 = reinterpret_cast<uint8_t const*>(data64 + n_blocks);
+    switch (len & 7U) {
+    case 7:
+        h ^= static_cast<uint64_t>(data8[6]) << 48U;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    case 6:
+        h ^= static_cast<uint64_t>(data8[5]) << 40U;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    case 5:
+        h ^= static_cast<uint64_t>(data8[4]) << 32U;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    case 4:
+        h ^= static_cast<uint64_t>(data8[3]) << 24U;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    case 3:
+        h ^= static_cast<uint64_t>(data8[2]) << 16U;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    case 2:
+        h ^= static_cast<uint64_t>(data8[1]) << 8U;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    case 1:
+        h ^= static_cast<uint64_t>(data8[0]);
+        h *= m;
+        ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+    default:
+        break;
+    }
+
+    h ^= h >> r;
+    h *= m;
+    h ^= h >> r;
+    return static_cast<size_t>(h);
+}
+
+inline size_t hash_int(uint64_t obj) noexcept {
+#if ROBIN_HOOD(HAS_UMUL128)
+    // 167079903232 masksum, 120428523 ops best: 0xde5fb9d2630458e9
+    static constexpr uint64_t k = UINT64_C(0xde5fb9d2630458e9);
+    uint64_t h;
+    uint64_t l = detail::umul128(obj, k, &h);
+    return h + l;
+#elif ROBIN_HOOD(BITNESS) == 32
+    uint64_t const r = obj * UINT64_C(0xca4bcaa75ec3f625);
+    auto h = static_cast<uint32_t>(r >> 32U);
+    auto l = static_cast<uint32_t>(r);
+    return h + l;
+#else
+    // murmurhash 3 finalizer
+    uint64_t h = obj;
+    h ^= h >> 33;
+    h *= 0xff51afd7ed558ccd;
+    h ^= h >> 33;
+    h *= 0xc4ceb9fe1a85ec53;
+    h ^= h >> 33;
+    return static_cast<size_t>(h);
+#endif
+}
+
+// A thin wrapper around std::hash, performing an additional simple mixing step of the result.
+template <typename T>
+struct hash : public std::hash<T> {
+    size_t operator()(T const& obj) const
+        noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>()))) {
+        // call base hash
+        auto result = std::hash<T>::operator()(obj);
+        // return mixed of that, to be save against identity has
+        return hash_int(static_cast<uint64_t>(result));
+    }
+};
+
+template <>
+struct hash<std::string> {
+    size_t operator()(std::string const& str) const noexcept {
+        return hash_bytes(str.data(), str.size());
+    }
+};
+
+template <class T>
+struct hash<T*> {
+    size_t operator()(T* ptr) const noexcept {
+        return hash_int(reinterpret_cast<size_t>(ptr));
+    }
+};
+
+#define ROBIN_HOOD_HASH_INT(T)                           \
+    template <>                                          \
+    struct hash<T> {                                     \
+        size_t operator()(T obj) const noexcept {        \
+            return hash_int(static_cast<uint64_t>(obj)); \
+        }                                                \
+    }
+
+#if defined(__GNUC__) && !defined(__clang__)
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif
+// see https://en.cppreference.com/w/cpp/utility/hash
+ROBIN_HOOD_HASH_INT(bool);
+ROBIN_HOOD_HASH_INT(char);
+ROBIN_HOOD_HASH_INT(signed char);
+ROBIN_HOOD_HASH_INT(unsigned char);
+ROBIN_HOOD_HASH_INT(char16_t);
+ROBIN_HOOD_HASH_INT(char32_t);
+ROBIN_HOOD_HASH_INT(wchar_t);
+ROBIN_HOOD_HASH_INT(short);
+ROBIN_HOOD_HASH_INT(unsigned short);
+ROBIN_HOOD_HASH_INT(int);
+ROBIN_HOOD_HASH_INT(unsigned int);
+ROBIN_HOOD_HASH_INT(long);
+ROBIN_HOOD_HASH_INT(long long);
+ROBIN_HOOD_HASH_INT(unsigned long);
+ROBIN_HOOD_HASH_INT(unsigned long long);
+#if defined(__GNUC__) && !defined(__clang__)
+#    pragma GCC diagnostic pop
+#endif
+namespace detail {
+
+// using wrapper classes for hash and key_equal prevents the diamond problem when the same type is
+// used. see https://stackoverflow.com/a/28771920/48181
+template <typename T>
+struct WrapHash : public T {
+    WrapHash() = default;
+    explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+        : T(o) {}
+};
+
+template <typename T>
+struct WrapKeyEqual : public T {
+    WrapKeyEqual() = default;
+    explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+        : T(o) {}
+};
+
+// A highly optimized hashmap implementation, using the Robin Hood algorithm.
+//
+// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but be
+// about 2x faster in most cases and require much less allocations.
+//
+// This implementation uses the following memory layout:
+//
+// [Node, Node, ... Node | info, info, ... infoSentinel ]
+//
+// * Node: either a DataNode that directly has the std::pair<key, val> as member,
+//   or a DataNode with a pointer to std::pair<key,val>. Which DataNode representation to use
+//   depends on how fast the swap() operation is. Heuristically, this is automatically choosen based
+//   on sizeof(). there are always 2^n Nodes.
+//
+// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes.
+//   Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the
+//   corresponding node contains data. Set to 2 means the corresponding Node is filled, but it
+//   actually belongs to the previous position and was pushed out because that place is already
+//   taken.
+//
+// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the need
+// for a idx
+//   variable.
+//
+// According to STL, order of templates has effect on throughput. That's why I've moved the boolean
+// to the front.
+// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/
+template <bool IsFlatMap, size_t MaxLoadFactor100, typename Key, typename T, typename Hash,
+          typename KeyEqual>
+class unordered_map
+    : public WrapHash<Hash>,
+      public WrapKeyEqual<KeyEqual>,
+      detail::NodeAllocator<
+          robin_hood::pair<typename std::conditional<IsFlatMap, Key, Key const>::type, T>, 4, 16384,
+          IsFlatMap> {
+public:
+    using key_type = Key;
+    using mapped_type = T;
+    using value_type =
+        robin_hood::pair<typename std::conditional<IsFlatMap, Key, Key const>::type, T>;
+    using size_type = size_t;
+    using hasher = Hash;
+    using key_equal = KeyEqual;
+    using Self =
+        unordered_map<IsFlatMap, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
+    static constexpr bool is_flat_map = IsFlatMap;
+
+private:
+    static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100,
+                  "MaxLoadFactor100 needs to be >10 && < 100");
+
+    using WHash = WrapHash<Hash>;
+    using WKeyEqual = WrapKeyEqual<KeyEqual>;
+
+    // configuration defaults
+
+    // make sure we have 8 elements, needed to quickly rehash mInfo
+    static constexpr size_t InitialNumElements = sizeof(uint64_t);
+    static constexpr uint32_t InitialInfoNumBits = 5;
+    static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits;
+    static constexpr uint8_t InitialInfoHashShift = sizeof(size_t) * 8 - InitialInfoNumBits;
+    using DataPool = detail::NodeAllocator<value_type, 4, 16384, IsFlatMap>;
+
+    // type needs to be wider than uint8_t.
+    using InfoType = uint32_t;
+
+    // DataNode ////////////////////////////////////////////////////////
+
+    // Primary template for the data node. We have special implementations for small and big
+    // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these on
+    // the heap so swap merely swaps a pointer.
+    template <typename M, bool>
+    class DataNode {};
+
+    // Small: just allocate on the stack.
+    template <typename M>
+    class DataNode<M, true> final {
+    public:
+        template <typename... Args>
+        explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept(
+            noexcept(value_type(std::forward<Args>(args)...)))
+            : mData(std::forward<Args>(args)...) {}
+
+        DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, true>&& n) noexcept(
+            std::is_nothrow_move_constructible<value_type>::value)
+            : mData(std::move(n.mData)) {}
+
+        // doesn't do anything
+        void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {}
+        void destroyDoNotDeallocate() noexcept {}
+
+        value_type const* operator->() const noexcept {
+            return &mData;
+        }
+        value_type* operator->() noexcept {
+            return &mData;
+        }
+
+        const value_type& operator*() const noexcept {
+            return mData;
+        }
+
+        value_type& operator*() noexcept {
+            return mData;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() noexcept {
+            return mData.first;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const noexcept {
+            return mData.first;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() noexcept {
+            return mData.second;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const noexcept {
+            return mData.second;
+        }
+
+        void swap(DataNode<M, true>& o) noexcept(
+            noexcept(std::declval<value_type>().swap(std::declval<value_type>()))) {
+            mData.swap(o.mData);
+        }
+
+    private:
+        value_type mData;
+    };
+
+    // big object: allocate on heap.
+    template <typename M>
+    class DataNode<M, false> {
+    public:
+        template <typename... Args>
+        explicit DataNode(M& map, Args&&... args)
+            : mData(map.allocate()) {
+            ::new (static_cast<void*>(mData)) value_type(std::forward<Args>(args)...);
+        }
+
+        DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, false>&& n) noexcept
+            : mData(std::move(n.mData)) {}
+
+        void destroy(M& map) noexcept {
+            // don't deallocate, just put it into list of datapool.
+            mData->~value_type();
+            map.deallocate(mData);
+        }
+
+        void destroyDoNotDeallocate() noexcept {
+            mData->~value_type();
+        }
+
+        value_type const* operator->() const noexcept {
+            return mData;
+        }
+
+        value_type* operator->() noexcept {
+            return mData;
+        }
+
+        const value_type& operator*() const {
+            return *mData;
+        }
+
+        value_type& operator*() {
+            return *mData;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() {
+            return mData->first;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const {
+            return mData->first;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() {
+            return mData->second;
+        }
+
+        ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const {
+            return mData->second;
+        }
+
+        void swap(DataNode<M, false>& o) noexcept {
+            using std::swap;
+            swap(mData, o.mData);
+        }
+
+    private:
+        value_type* mData;
+    };
+
+    using Node = DataNode<Self, IsFlatMap>;
+
+    // Cloner //////////////////////////////////////////////////////////
+
+    template <typename M, bool UseMemcpy>
+    struct Cloner;
+
+    // fast path: Just copy data, without allocating anything.
+    template <typename M>
+    struct Cloner<M, true> {
+        void operator()(M const& source, M& target) const {
+            // std::memcpy(target.mKeyVals, source.mKeyVals,
+            //             target.calcNumBytesTotal(target.mMask + 1));
+            auto src = reinterpret_cast<char const*>(source.mKeyVals);
+            auto tgt = reinterpret_cast<char*>(target.mKeyVals);
+            std::copy(src, src + target.calcNumBytesTotal(target.mMask + 1), tgt);
+        }
+    };
+
+    template <typename M>
+    struct Cloner<M, false> {
+        void operator()(M const& s, M& t) const {
+            std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(t.mMask + 1), t.mInfo);
+
+            for (size_t i = 0; i < t.mMask + 1; ++i) {
+                if (t.mInfo[i]) {
+                    ::new (static_cast<void*>(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]);
+                }
+            }
+        }
+    };
+
+    // Destroyer ///////////////////////////////////////////////////////
+
+    template <typename M, bool IsFlatMapAndTrivial>
+    struct Destroyer {};
+
+    template <typename M>
+    struct Destroyer<M, true> {
+        void nodes(M& m) const noexcept {
+            m.mNumElements = 0;
+        }
+
+        void nodesDoNotDeallocate(M& m) const noexcept {
+            m.mNumElements = 0;
+        }
+    };
+
+    template <typename M>
+    struct Destroyer<M, false> {
+        void nodes(M& m) const noexcept {
+            m.mNumElements = 0;
+            // clear also resets mInfo to 0, that's sometimes not necessary.
+            for (size_t idx = 0; idx <= m.mMask; ++idx) {
+                if (0 != m.mInfo[idx]) {
+                    Node& n = m.mKeyVals[idx];
+                    n.destroy(m);
+                    n.~Node();
+                }
+            }
+        }
+
+        void nodesDoNotDeallocate(M& m) const noexcept {
+            m.mNumElements = 0;
+            // clear also resets mInfo to 0, that's sometimes not necessary.
+            for (size_t idx = 0; idx <= m.mMask; ++idx) {
+                if (0 != m.mInfo[idx]) {
+                    Node& n = m.mKeyVals[idx];
+                    n.destroyDoNotDeallocate();
+                    n.~Node();
+                }
+            }
+        }
+    };
+
+    // Iter ////////////////////////////////////////////////////////////
+
+    struct fast_forward_tag {};
+
+    // generic iterator for both const_iterator and iterator.
+    template <bool IsConst>
+    // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions)
+    class Iter {
+    private:
+        using NodePtr = typename std::conditional<IsConst, Node const*, Node*>::type;
+
+    public:
+        using difference_type = std::ptrdiff_t;
+        using value_type = typename Self::value_type;
+        using reference = typename std::conditional<IsConst, value_type const&, value_type&>::type;
+        using pointer = typename std::conditional<IsConst, value_type const*, value_type*>::type;
+        using iterator_category = std::forward_iterator_tag;
+
+        // default constructed iterator can be compared to itself, but WON'T return true when
+        // compared to end().
+        Iter() = default;
+
+        // Rule of zero: nothing specified. The conversion constructor is only enabled for iterator
+        // to const_iterator, so it doesn't accidentally work as a copy ctor.
+
+        // Conversion constructor from iterator to const_iterator.
+        template <bool OtherIsConst,
+                  typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+        // NOLINTNEXTLINE(hicpp-explicit-conversions)
+        Iter(Iter<OtherIsConst> const& other) noexcept
+            : mKeyVals(other.mKeyVals)
+            , mInfo(other.mInfo) {}
+
+        Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept
+            : mKeyVals(valPtr)
+            , mInfo(infoPtr) {}
+
+        Iter(NodePtr valPtr, uint8_t const* infoPtr,
+             fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept
+            : mKeyVals(valPtr)
+            , mInfo(infoPtr) {
+            fastForward();
+        }
+
+        template <bool OtherIsConst,
+                  typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+        Iter& operator=(Iter<OtherIsConst> const& other) noexcept {
+            mKeyVals = other.mKeyVals;
+            mInfo = other.mInfo;
+            return *this;
+        }
+
+        // prefix increment. Undefined behavior if we are at end()!
+        Iter& operator++() noexcept {
+            mInfo++;
+            mKeyVals++;
+            fastForward();
+            return *this;
+        }
+
+        reference operator*() const {
+            return **mKeyVals;
+        }
+
+        pointer operator->() const {
+            return &**mKeyVals;
+        }
+
+        template <bool O>
+        bool operator==(Iter<O> const& o) const noexcept {
+            return mKeyVals == o.mKeyVals;
+        }
+
+        template <bool O>
+        bool operator!=(Iter<O> const& o) const noexcept {
+            return mKeyVals != o.mKeyVals;
+        }
+
+    private:
+        // fast forward to the next non-free info byte
+        void fastForward() noexcept {
+            int inc;
+            do {
+                auto const n = detail::unaligned_load<size_t>(mInfo);
+#if ROBIN_HOOD(LITTLE_ENDIAN)
+                inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
+#else
+                inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
+#endif
+                mInfo += inc;
+                mKeyVals += inc;
+            } while (inc == static_cast<int>(sizeof(size_t)));
+        }
+
+        friend class unordered_map<IsFlatMap, MaxLoadFactor100, key_type, mapped_type, hasher,
+                                   key_equal>;
+        NodePtr mKeyVals{nullptr};
+        uint8_t const* mInfo{nullptr};
+    };
+
+    ////////////////////////////////////////////////////////////////////
+
+    // highly performance relevant code.
+    // Lower bits are used for indexing into the array (2^n size)
+    // The upper 1-5 bits need to be a reasonable good hash, to save comparisons.
+    template <typename HashKey>
+    void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const {
+        // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as an
+        // additional mixing step. This serves as a bad hash prevention, if the given data is badly
+        // mixed.
+        using Mix =
+            typename std::conditional<std::is_same<::robin_hood::hash<key_type>, hasher>::value,
+                                      ::robin_hood::detail::identity_hash<size_t>,
+                                      ::robin_hood::hash<size_t>>::type;
+        *idx = Mix{}(WHash::operator()(key));
+
+        *info = mInfoInc + static_cast<InfoType>(*idx >> mInfoHashShift);
+        *idx &= mMask;
+    }
+
+    // forwards the index by one, wrapping around at the end
+    void next(InfoType* info, size_t* idx) const noexcept {
+        *idx = (*idx + 1) & mMask;
+        *info += mInfoInc;
+    }
+
+    void nextWhileLess(InfoType* info, size_t* idx) const noexcept {
+        // unrolling this by hand did not bring any speedups.
+        while (*info < mInfo[*idx]) {
+            next(info, idx);
+        }
+    }
+
+    // Shift everything up by one element. Tries to move stuff around.
+    // True if some shifting has occured (entry under idx is a constructed object)
+    // Fals if no shift has occured (entry under idx is unconstructed memory)
+    void
+    shiftUp(size_t idx,
+            size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+        while (idx != insertion_idx) {
+            size_t prev_idx = (idx - 1) & mMask;
+            if (mInfo[idx]) {
+                mKeyVals[idx] = std::move(mKeyVals[prev_idx]);
+            } else {
+                ::new (static_cast<void*>(mKeyVals + idx)) Node(std::move(mKeyVals[prev_idx]));
+            }
+            mInfo[idx] = static_cast<uint8_t>(mInfo[prev_idx] + mInfoInc);
+            if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) {
+                mMaxNumElementsAllowed = 0;
+            }
+            idx = prev_idx;
+        }
+    }
+
+    void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+        // until we find one that is either empty or has zero offset.
+        // TODO(martinus) we don't need to move everything, just the last one for the same bucket.
+        mKeyVals[idx].destroy(*this);
+
+        // until we find one that is either empty or has zero offset.
+        size_t nextIdx = (idx + 1) & mMask;
+        while (mInfo[nextIdx] >= 2 * mInfoInc) {
+            mInfo[idx] = static_cast<uint8_t>(mInfo[nextIdx] - mInfoInc);
+            mKeyVals[idx] = std::move(mKeyVals[nextIdx]);
+            idx = nextIdx;
+            nextIdx = (idx + 1) & mMask;
+        }
+
+        mInfo[idx] = 0;
+        // don't destroy, we've moved it
+        // mKeyVals[idx].destroy(*this);
+        mKeyVals[idx].~Node();
+    }
+
+    // copy of find(), except that it returns iterator instead of const_iterator.
+    template <typename Other>
+    ROBIN_HOOD(NODISCARD)
+    size_t findIdx(Other const& key) const {
+        size_t idx;
+        InfoType info;
+        keyToIdx(key, &idx, &info);
+
+        do {
+            // unrolling this twice gives a bit of a speedup. More unrolling did not help.
+            if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+                return idx;
+            }
+            next(&info, &idx);
+            if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+                return idx;
+            }
+            next(&info, &idx);
+        } while (info <= mInfo[idx]);
+
+        // nothing found!
+        return mMask == 0 ? 0 : mMask + 1;
+    }
+
+    void cloneData(const unordered_map& o) {
+        Cloner<unordered_map, IsFlatMap && ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(Node)>()(o, *this);
+    }
+
+    // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized.
+    // @return index where the element was created
+    size_t insert_move(Node&& keyval) {
+        // we don't retry, fail if overflowing
+        // don't need to check max num elements
+        if (0 == mMaxNumElementsAllowed && !try_increase_info()) {
+            throwOverflowError();
+        }
+
+        size_t idx;
+        InfoType info;
+        keyToIdx(keyval.getFirst(), &idx, &info);
+
+        // skip forward. Use <= because we are certain that the element is not there.
+        while (info <= mInfo[idx]) {
+            idx = (idx + 1) & mMask;
+            info += mInfoInc;
+        }
+
+        // key not found, so we are now exactly where we want to insert it.
+        auto const insertion_idx = idx;
+        auto const insertion_info = static_cast<uint8_t>(info);
+        if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+            mMaxNumElementsAllowed = 0;
+        }
+
+        // find an empty spot
+        while (0 != mInfo[idx]) {
+            next(&info, &idx);
+        }
+
+        auto& l = mKeyVals[insertion_idx];
+        if (idx == insertion_idx) {
+            ::new (static_cast<void*>(&l)) Node(std::move(keyval));
+        } else {
+            shiftUp(idx, insertion_idx);
+            l = std::move(keyval);
+        }
+
+        // put at empty spot
+        mInfo[insertion_idx] = insertion_info;
+
+        ++mNumElements;
+        return insertion_idx;
+    }
+
+public:
+    using iterator = Iter<false>;
+    using const_iterator = Iter<true>;
+
+    // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. This
+    // tremendously speeds up ctor & dtor of a map that never receives an element. The penalty is
+    // payed at the first insert, and not before. Lookup of this empty map works because everybody
+    // points to DummyInfoByte::b. parameter bucket_count is dictated by the standard, but we can
+    // ignore it.
+    explicit unordered_map(size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0,
+                           const Hash& h = Hash{},
+                           const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) &&
+                                                                        noexcept(KeyEqual(equal)))
+        : WHash(h)
+        , WKeyEqual(equal) {
+        ROBIN_HOOD_TRACE(this);
+    }
+
+    template <typename Iter>
+    unordered_map(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0,
+                  const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{})
+        : WHash(h)
+        , WKeyEqual(equal) {
+        ROBIN_HOOD_TRACE(this);
+        insert(first, last);
+    }
+
+    unordered_map(std::initializer_list<value_type> initlist,
+                  size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{},
+                  const KeyEqual& equal = KeyEqual{})
+        : WHash(h)
+        , WKeyEqual(equal) {
+        ROBIN_HOOD_TRACE(this);
+        insert(initlist.begin(), initlist.end());
+    }
+
+    unordered_map(unordered_map&& o) noexcept
+        : WHash(std::move(static_cast<WHash&>(o)))
+        , WKeyEqual(std::move(static_cast<WKeyEqual&>(o)))
+        , DataPool(std::move(static_cast<DataPool&>(o))) {
+        ROBIN_HOOD_TRACE(this);
+        if (o.mMask) {
+            mKeyVals = std::move(o.mKeyVals);
+            mInfo = std::move(o.mInfo);
+            mNumElements = std::move(o.mNumElements);
+            mMask = std::move(o.mMask);
+            mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+            mInfoInc = std::move(o.mInfoInc);
+            mInfoHashShift = std::move(o.mInfoHashShift);
+            // set other's mask to 0 so its destructor won't do anything
+            o.init();
+        }
+    }
+
+    unordered_map& operator=(unordered_map&& o) noexcept {
+        ROBIN_HOOD_TRACE(this);
+        if (&o != this) {
+            if (o.mMask) {
+                // only move stuff if the other map actually has some data
+                destroy();
+                mKeyVals = std::move(o.mKeyVals);
+                mInfo = std::move(o.mInfo);
+                mNumElements = std::move(o.mNumElements);
+                mMask = std::move(o.mMask);
+                mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+                mInfoInc = std::move(o.mInfoInc);
+                mInfoHashShift = std::move(o.mInfoHashShift);
+                WHash::operator=(std::move(static_cast<WHash&>(o)));
+                WKeyEqual::operator=(std::move(static_cast<WKeyEqual&>(o)));
+                DataPool::operator=(std::move(static_cast<DataPool&>(o)));
+
+                o.init();
+
+            } else {
+                // nothing in the other map => just clear us.
+                clear();
+            }
+        }
+        return *this;
+    }
+
+    unordered_map(const unordered_map& o)
+        : WHash(static_cast<const WHash&>(o))
+        , WKeyEqual(static_cast<const WKeyEqual&>(o))
+        , DataPool(static_cast<const DataPool&>(o)) {
+        ROBIN_HOOD_TRACE(this);
+        if (!o.empty()) {
+            // not empty: create an exact copy. it is also possible to just iterate through all
+            // elements and insert them, but copying is probably faster.
+
+            mKeyVals = static_cast<Node*>(
+                detail::assertNotNull<std::bad_alloc>(malloc(calcNumBytesTotal(o.mMask + 1))));
+            // no need for calloc because clonData does memcpy
+            mInfo = reinterpret_cast<uint8_t*>(mKeyVals + o.mMask + 1);
+            mNumElements = o.mNumElements;
+            mMask = o.mMask;
+            mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+            mInfoInc = o.mInfoInc;
+            mInfoHashShift = o.mInfoHashShift;
+            cloneData(o);
+        }
+    }
+
+    // Creates a copy of the given map. Copy constructor of each entry is used.
+    unordered_map& operator=(unordered_map const& o) {
+        ROBIN_HOOD_TRACE(this);
+        if (&o == this) {
+            // prevent assigning of itself
+            return *this;
+        }
+
+        // we keep using the old allocator and not assign the new one, because we want to keep the
+        // memory available. when it is the same size.
+        if (o.empty()) {
+            if (0 == mMask) {
+                // nothing to do, we are empty too
+                return *this;
+            }
+
+            // not empty: destroy what we have there
+            // clear also resets mInfo to 0, that's sometimes not necessary.
+            destroy();
+            init();
+            WHash::operator=(static_cast<const WHash&>(o));
+            WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+            DataPool::operator=(static_cast<DataPool const&>(o));
+
+            return *this;
+        }
+
+        // clean up old stuff
+        Destroyer<Self, IsFlatMap && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+        if (mMask != o.mMask) {
+            // no luck: we don't have the same array size allocated, so we need to realloc.
+            if (0 != mMask) {
+                // only deallocate if we actually have data!
+                free(mKeyVals);
+            }
+
+            mKeyVals = static_cast<Node*>(
+                detail::assertNotNull<std::bad_alloc>(malloc(calcNumBytesTotal(o.mMask + 1))));
+
+            // no need for calloc here because cloneData performs a memcpy.
+            mInfo = reinterpret_cast<uint8_t*>(mKeyVals + o.mMask + 1);
+            // sentinel is set in cloneData
+        }
+        WHash::operator=(static_cast<const WHash&>(o));
+        WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+        DataPool::operator=(static_cast<DataPool const&>(o));
+        mNumElements = o.mNumElements;
+        mMask = o.mMask;
+        mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+        mInfoInc = o.mInfoInc;
+        mInfoHashShift = o.mInfoHashShift;
+        cloneData(o);
+
+        return *this;
+    }
+
+    // Swaps everything between the two maps.
+    void swap(unordered_map& o) {
+        ROBIN_HOOD_TRACE(this);
+        using std::swap;
+        swap(o, *this);
+    }
+
+    // Clears all data, without resizing.
+    void clear() {
+        ROBIN_HOOD_TRACE(this);
+        if (empty()) {
+            // don't do anything! also important because we don't want to write to DummyInfoByte::b,
+            // even though we would just write 0 to it.
+            return;
+        }
+
+        Destroyer<Self, IsFlatMap && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+        // clear everything except the sentinel
+        // std::memset(mInfo, 0, sizeof(uint8_t) * (mMask + 1));
+        uint8_t const z = 0;
+        std::fill(mInfo, mInfo + (sizeof(uint8_t) * (mMask + 1)), z);
+
+        mInfoInc = InitialInfoInc;
+        mInfoHashShift = InitialInfoHashShift;
+    }
+
+    // Destroys the map and all it's contents.
+    ~unordered_map() {
+        ROBIN_HOOD_TRACE(this);
+        destroy();
+    }
+
+    // Checks if both maps contain the same entries. Order is irrelevant.
+    bool operator==(const unordered_map& other) const {
+        ROBIN_HOOD_TRACE(this);
+        if (other.size() != size()) {
+            return false;
+        }
+        for (auto const& otherEntry : other) {
+            auto const myIt = find(otherEntry.first);
+            if (myIt == end() || !(myIt->second == otherEntry.second)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    bool operator!=(const unordered_map& other) const {
+        ROBIN_HOOD_TRACE(this);
+        return !operator==(other);
+    }
+
+    mapped_type& operator[](const key_type& key) {
+        ROBIN_HOOD_TRACE(this);
+        return doCreateByKey(key);
+    }
+
+    mapped_type& operator[](key_type&& key) {
+        ROBIN_HOOD_TRACE(this);
+        return doCreateByKey(std::move(key));
+    }
+
+    template <typename Iter>
+    void insert(Iter first, Iter last) {
+        for (; first != last; ++first) {
+            // value_type ctor needed because this might be called with std::pair's
+            insert(value_type(*first));
+        }
+    }
+
+    template <typename... Args>
+    std::pair<iterator, bool> emplace(Args&&... args) {
+        ROBIN_HOOD_TRACE(this);
+        Node n{*this, std::forward<Args>(args)...};
+        auto r = doInsert(std::move(n));
+        if (!r.second) {
+            // insertion not possible: destroy node
+            // NOLINTNEXTLINE(bugprone-use-after-move)
+            n.destroy(*this);
+        }
+        return r;
+    }
+
+    std::pair<iterator, bool> insert(const value_type& keyval) {
+        ROBIN_HOOD_TRACE(this);
+        return doInsert(keyval);
+    }
+
+    std::pair<iterator, bool> insert(value_type&& keyval) {
+        return doInsert(std::move(keyval));
+    }
+
+    // Returns 1 if key is found, 0 otherwise.
+    size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        auto kv = mKeyVals + findIdx(key);
+        if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+            return 1;
+        }
+        return 0;
+    }
+
+    // Returns a reference to the value found for key.
+    // Throws std::out_of_range if element cannot be found
+    mapped_type& at(key_type const& key) {
+        ROBIN_HOOD_TRACE(this);
+        auto kv = mKeyVals + findIdx(key);
+        if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+            doThrow<std::out_of_range>("key not found");
+        }
+        return kv->getSecond();
+    }
+
+    // Returns a reference to the value found for key.
+    // Throws std::out_of_range if element cannot be found
+    mapped_type const& at(key_type const& key) const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        auto kv = mKeyVals + findIdx(key);
+        if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+            doThrow<std::out_of_range>("key not found");
+        }
+        return kv->getSecond();
+    }
+
+    const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        const size_t idx = findIdx(key);
+        return const_iterator{mKeyVals + idx, mInfo + idx};
+    }
+
+    template <typename OtherKey>
+    const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const {
+        ROBIN_HOOD_TRACE(this);
+        const size_t idx = findIdx(key);
+        return const_iterator{mKeyVals + idx, mInfo + idx};
+    }
+
+    iterator find(const key_type& key) {
+        ROBIN_HOOD_TRACE(this);
+        const size_t idx = findIdx(key);
+        return iterator{mKeyVals + idx, mInfo + idx};
+    }
+
+    template <typename OtherKey>
+    iterator find(const OtherKey& key, is_transparent_tag /*unused*/) {
+        ROBIN_HOOD_TRACE(this);
+        const size_t idx = findIdx(key);
+        return iterator{mKeyVals + idx, mInfo + idx};
+    }
+
+    iterator begin() {
+        ROBIN_HOOD_TRACE(this);
+        if (empty()) {
+            return end();
+        }
+        return iterator(mKeyVals, mInfo, fast_forward_tag{});
+    }
+    const_iterator begin() const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return cbegin();
+    }
+    const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        if (empty()) {
+            return cend();
+        }
+        return const_iterator(mKeyVals, mInfo, fast_forward_tag{});
+    }
+
+    iterator end() {
+        ROBIN_HOOD_TRACE(this);
+        // no need to supply valid info pointer: end() must not be dereferenced, and only node
+        // pointer is compared.
+        return iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+    }
+    const_iterator end() const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return cend();
+    }
+    const_iterator cend() const { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return const_iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+    }
+
+    iterator erase(const_iterator pos) {
+        ROBIN_HOOD_TRACE(this);
+        // its safe to perform const cast here
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+        return erase(iterator{const_cast<Node*>(pos.mKeyVals), const_cast<uint8_t*>(pos.mInfo)});
+    }
+
+    // Erases element at pos, returns iterator to the next element.
+    iterator erase(iterator pos) {
+        ROBIN_HOOD_TRACE(this);
+        // we assume that pos always points to a valid entry, and not end().
+        auto const idx = static_cast<size_t>(pos.mKeyVals - mKeyVals);
+
+        shiftDown(idx);
+        --mNumElements;
+
+        if (*pos.mInfo) {
+            // we've backward shifted, return this again
+            return pos;
+        }
+
+        // no backward shift, return next element
+        return ++pos;
+    }
+
+    size_t erase(const key_type& key) {
+        ROBIN_HOOD_TRACE(this);
+        size_t idx;
+        InfoType info;
+        keyToIdx(key, &idx, &info);
+
+        // check while info matches with the source idx
+        do {
+            if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+                shiftDown(idx);
+                --mNumElements;
+                return 1;
+            }
+            next(&info, &idx);
+        } while (info <= mInfo[idx]);
+
+        // nothing found to delete
+        return 0;
+    }
+
+    // reserves space for the specified number of elements. Makes sure the old data fits.
+    // exactly the same as reserve(c).
+    void rehash(size_t c) {
+        reserve(c);
+    }
+
+    // reserves space for the specified number of elements. Makes sure the old data fits.
+    // Exactly the same as resize(c). Use resize(0) to shrink to fit.
+    void reserve(size_t c) {
+        ROBIN_HOOD_TRACE(this);
+        auto const minElementsAllowed = (std::max)(c, mNumElements);
+        auto newSize = InitialNumElements;
+        while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) {
+            newSize *= 2;
+        }
+        if (ROBIN_HOOD_UNLIKELY(newSize == 0)) {
+            throwOverflowError();
+        }
+
+        rehashPowerOfTwo(newSize);
+    }
+
+    size_type size() const noexcept { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return mNumElements;
+    }
+
+    size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return static_cast<size_type>(-1);
+    }
+
+    ROBIN_HOOD(NODISCARD) bool empty() const noexcept {
+        ROBIN_HOOD_TRACE(this);
+        return 0 == mNumElements;
+    }
+
+    float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return MaxLoadFactor100 / 100.0F;
+    }
+
+    // Average number of elements per bucket. Since we allow only 1 per bucket
+    float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+        ROBIN_HOOD_TRACE(this);
+        return static_cast<float>(size()) / static_cast<float>(mMask + 1);
+    }
+
+    ROBIN_HOOD(NODISCARD) size_t mask() const noexcept {
+        ROBIN_HOOD_TRACE(this);
+        return mMask;
+    }
+
+    ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept {
+        if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits<size_t>::max)() / 100)) {
+            return maxElements * MaxLoadFactor100 / 100;
+        }
+
+        // we might be a bit inprecise, but since maxElements is quite large that doesn't matter
+        return (maxElements / 100) * MaxLoadFactor100;
+    }
+
+    ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const {
+        return numElements + sizeof(uint64_t);
+    }
+
+    // calculation ony allowed for 2^n values
+    ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const {
+#if ROBIN_HOOD(BITNESS) == 64
+        return numElements * sizeof(Node) + calcNumBytesInfo(numElements);
+#else
+        // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows.
+        auto const ne = static_cast<uint64_t>(numElements);
+        auto const s = static_cast<uint64_t>(sizeof(Node));
+        auto const infos = static_cast<uint64_t>(calcNumBytesInfo(numElements));
+
+        auto const total64 = ne * s + infos;
+        auto const total = static_cast<size_t>(total64);
+
+        if (ROBIN_HOOD_UNLIKELY(static_cast<uint64_t>(total) != total64)) {
+            throwOverflowError();
+        }
+        return total;
+#endif
+    }
+
+private:
+    // reserves space for at least the specified number of elements.
+    // only works if numBuckets if power of two
+    void rehashPowerOfTwo(size_t numBuckets) {
+        ROBIN_HOOD_TRACE(this);
+
+        Node* const oldKeyVals = mKeyVals;
+        uint8_t const* const oldInfo = mInfo;
+
+        const size_t oldMaxElements = mMask + 1;
+
+        // resize operation: move stuff
+        init_data(numBuckets);
+        if (oldMaxElements > 1) {
+            for (size_t i = 0; i < oldMaxElements; ++i) {
+                if (oldInfo[i] != 0) {
+                    insert_move(std::move(oldKeyVals[i]));
+                    // destroy the node but DON'T destroy the data.
+                    oldKeyVals[i].~Node();
+                }
+            }
+
+            // don't destroy old data: put it into the pool instead
+            DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElements));
+        }
+    }
+
+    ROBIN_HOOD(NOINLINE) void throwOverflowError() const {
+#if ROBIN_HOOD(HAS_EXCEPTIONS)
+        throw std::overflow_error("robin_hood::map overflow");
+#else
+        abort();
+#endif
+    }
+
+    void init_data(size_t max_elements) {
+        mNumElements = 0;
+        mMask = max_elements - 1;
+        mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements);
+
+        // calloc also zeroes everything
+        mKeyVals = reinterpret_cast<Node*>(
+            detail::assertNotNull<std::bad_alloc>(calloc(1, calcNumBytesTotal(max_elements))));
+        mInfo = reinterpret_cast<uint8_t*>(mKeyVals + max_elements);
+
+        // set sentinel
+        mInfo[max_elements] = 1;
+
+        mInfoInc = InitialInfoInc;
+        mInfoHashShift = InitialInfoHashShift;
+    }
+
+    template <typename Arg>
+    mapped_type& doCreateByKey(Arg&& key) {
+        while (true) {
+            size_t idx;
+            InfoType info;
+            keyToIdx(key, &idx, &info);
+            nextWhileLess(&info, &idx);
+
+            // while we potentially have a match. Can't do a do-while here because when mInfo is 0
+            // we don't want to skip forward
+            while (info == mInfo[idx]) {
+                if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+                    // key already exists, do not insert.
+                    return mKeyVals[idx].getSecond();
+                }
+                next(&info, &idx);
+            }
+
+            // unlikely that this evaluates to true
+            if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) {
+                increase_size();
+                continue;
+            }
+
+            // key not found, so we are now exactly where we want to insert it.
+            auto const insertion_idx = idx;
+            auto const insertion_info = info;
+            if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+                mMaxNumElementsAllowed = 0;
+            }
+
+            // find an empty spot
+            while (0 != mInfo[idx]) {
+                next(&info, &idx);
+            }
+
+            auto& l = mKeyVals[insertion_idx];
+            if (idx == insertion_idx) {
+                // put at empty spot. This forwards all arguments into the node where the object is
+                // constructed exactly where it is needed.
+                ::new (static_cast<void*>(&l))
+                    Node(*this, std::piecewise_construct,
+                         std::forward_as_tuple(std::forward<Arg>(key)), std::forward_as_tuple());
+            } else {
+                shiftUp(idx, insertion_idx);
+                l = Node(*this, std::piecewise_construct,
+                         std::forward_as_tuple(std::forward<Arg>(key)), std::forward_as_tuple());
+            }
+
+            // mKeyVals[idx].getFirst() = std::move(key);
+            mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
+
+            ++mNumElements;
+            return mKeyVals[insertion_idx].getSecond();
+        }
+    }
+
+    // This is exactly the same code as operator[], except for the return values
+    template <typename Arg>
+    std::pair<iterator, bool> doInsert(Arg&& keyval) {
+        while (true) {
+            size_t idx;
+            InfoType info;
+            keyToIdx(keyval.getFirst(), &idx, &info);
+            nextWhileLess(&info, &idx);
+
+            // while we potentially have a match
+            while (info == mInfo[idx]) {
+                if (WKeyEqual::operator()(keyval.getFirst(), mKeyVals[idx].getFirst())) {
+                    // key already exists, do NOT insert.
+                    // see http://en.cppreference.com/w/cpp/container/unordered_map/insert
+                    return std::make_pair<iterator, bool>(iterator(mKeyVals + idx, mInfo + idx),
+                                                          false);
+                }
+                next(&info, &idx);
+            }
+
+            // unlikely that this evaluates to true
+            if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) {
+                increase_size();
+                continue;
+            }
+
+            // key not found, so we are now exactly where we want to insert it.
+            auto const insertion_idx = idx;
+            auto const insertion_info = info;
+            if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+                mMaxNumElementsAllowed = 0;
+            }
+
+            // find an empty spot
+            while (0 != mInfo[idx]) {
+                next(&info, &idx);
+            }
+
+            auto& l = mKeyVals[insertion_idx];
+            if (idx == insertion_idx) {
+                ::new (static_cast<void*>(&l)) Node(*this, std::forward<Arg>(keyval));
+            } else {
+                shiftUp(idx, insertion_idx);
+                l = Node(*this, std::forward<Arg>(keyval));
+            }
+
+            // put at empty spot
+            mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
+
+            ++mNumElements;
+            return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true);
+        }
+    }
+
+    bool try_increase_info() {
+        ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements
+                                   << ", maxNumElementsAllowed="
+                                   << calcMaxNumElementsAllowed(mMask + 1));
+        if (mInfoInc <= 2) {
+            // need to be > 2 so that shift works (otherwise undefined behavior!)
+            return false;
+        }
+        // we got space left, try to make info smaller
+        mInfoInc = static_cast<uint8_t>(mInfoInc >> 1U);
+
+        // remove one bit of the hash, leaving more space for the distance info.
+        // This is extremely fast because we can operate on 8 bytes at once.
+        ++mInfoHashShift;
+        auto const data = reinterpret_cast_no_cast_align_warning<uint64_t*>(mInfo);
+        auto const numEntries = (mMask + 1) / 8;
+
+        for (size_t i = 0; i < numEntries; ++i) {
+            data[i] = (data[i] >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f);
+        }
+        mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+        return true;
+    }
+
+    void increase_size() {
+        // nothing allocated yet? just allocate InitialNumElements
+        if (0 == mMask) {
+            init_data(InitialNumElements);
+            return;
+        }
+
+        auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+        if (mNumElements < maxNumElementsAllowed && try_increase_info()) {
+            return;
+        }
+
+        ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed="
+                                       << maxNumElementsAllowed << ", load="
+                                       << (static_cast<double>(mNumElements) * 100.0 /
+                                           (static_cast<double>(mMask) + 1)));
+        // it seems we have a really bad hash function! don't try to resize again
+        if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) {
+            throwOverflowError();
+        }
+
+        rehashPowerOfTwo((mMask + 1) * 2);
+    }
+
+    void destroy() {
+        if (0 == mMask) {
+            // don't deallocate!
+            return;
+        }
+
+        Destroyer<Self, IsFlatMap && std::is_trivially_destructible<Node>::value>{}
+            .nodesDoNotDeallocate(*this);
+        free(mKeyVals);
+    }
+
+    void init() noexcept {
+        mKeyVals = reinterpret_cast<Node*>(&mMask);
+        mInfo = reinterpret_cast<uint8_t*>(&mMask);
+        mNumElements = 0;
+        mMask = 0;
+        mMaxNumElementsAllowed = 0;
+        mInfoInc = InitialInfoInc;
+        mInfoHashShift = InitialInfoHashShift;
+    }
+
+    // members are sorted so no padding occurs
+    Node* mKeyVals = reinterpret_cast<Node*>(&mMask);    // 8 byte  8
+    uint8_t* mInfo = reinterpret_cast<uint8_t*>(&mMask); // 8 byte 16
+    size_t mNumElements = 0;                             // 8 byte 24
+    size_t mMask = 0;                                    // 8 byte 32
+    size_t mMaxNumElementsAllowed = 0;                   // 8 byte 40
+    InfoType mInfoInc = InitialInfoInc;                  // 4 byte 44
+    InfoType mInfoHashShift = InitialInfoHashShift;      // 4 byte 48
+                                                         // 16 byte 56 if NodeAllocator
+};
+
+} // namespace detail
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+          typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_flat_map = detail::unordered_map<true, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+          typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_node_map = detail::unordered_map<false, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+          typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_map =
+    detail::unordered_map<sizeof(robin_hood::pair<Key, T>) <= sizeof(size_t) * 6 &&
+                              std::is_nothrow_move_constructible<robin_hood::pair<Key, T>>::value &&
+                              std::is_nothrow_move_assignable<robin_hood::pair<Key, T>>::value,
+                          MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+} // namespace robin_hood
+
+#endif