Home | History | Annotate | Line # | Download | only in gdbsupport
      1  1.1  christos ///////////////////////// ankerl::unordered_dense::{map, set} /////////////////////////
      2  1.1  christos 
      3  1.1  christos // A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion.
      4  1.1  christos // Version 4.4.0
      5  1.1  christos // https://github.com/martinus/unordered_dense
      6  1.1  christos //
      7  1.1  christos // Licensed under the MIT License <http://opensource.org/licenses/MIT>.
      8  1.1  christos // SPDX-License-Identifier: MIT
      9  1.1  christos // Copyright (c) 2022-2023 Martin Leitner-Ankerl <martin.ankerl (at) gmail.com>
     10  1.1  christos //
     11  1.1  christos // Permission is hereby granted, free of charge, to any person obtaining a copy
     12  1.1  christos // of this software and associated documentation files (the "Software"), to deal
     13  1.1  christos // in the Software without restriction, including without limitation the rights
     14  1.1  christos // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     15  1.1  christos // copies of the Software, and to permit persons to whom the Software is
     16  1.1  christos // furnished to do so, subject to the following conditions:
     17  1.1  christos //
     18  1.1  christos // The above copyright notice and this permission notice shall be included in all
     19  1.1  christos // copies or substantial portions of the Software.
     20  1.1  christos //
     21  1.1  christos // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     22  1.1  christos // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     23  1.1  christos // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     24  1.1  christos // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     25  1.1  christos // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     26  1.1  christos // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     27  1.1  christos // SOFTWARE.
     28  1.1  christos 
     29  1.1  christos #ifndef ANKERL_UNORDERED_DENSE_H
     30  1.1  christos #define ANKERL_UNORDERED_DENSE_H
     31  1.1  christos 
     32  1.1  christos // see https://semver.org/spec/v2.0.0.html
     33  1.1  christos #define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes
     34  1.1  christos #define ANKERL_UNORDERED_DENSE_VERSION_MINOR 4 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality
     35  1.1  christos #define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes
     36  1.1  christos 
     37  1.1  christos // API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/
     38  1.1  christos 
     39  1.1  christos // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
     40  1.1  christos #define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch
     41  1.1  christos // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
     42  1.1  christos #define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch)
     43  1.1  christos #define ANKERL_UNORDERED_DENSE_NAMESPACE   \
     44  1.1  christos     ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \
     45  1.1  christos         ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH)
     46  1.1  christos 
     47  1.1  christos #if defined(_MSVC_LANG)
     48  1.1  christos #    define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG
     49  1.1  christos #else
     50  1.1  christos #    define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus
     51  1.1  christos #endif
     52  1.1  christos 
     53  1.1  christos #if defined(__GNUC__)
     54  1.1  christos // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
     55  1.1  christos #    define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__))
     56  1.1  christos #elif defined(_MSC_VER)
     57  1.1  christos // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
     58  1.1  christos #    define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop))
     59  1.1  christos #endif
     60  1.1  christos 
     61  1.1  christos // exceptions
     62  1.1  christos #if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
     63  1.1  christos #    define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 // NOLINT(cppcoreguidelines-macro-usage)
     64  1.1  christos #else
     65  1.1  christos #    define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 // NOLINT(cppcoreguidelines-macro-usage)
     66  1.1  christos #endif
     67  1.1  christos #ifdef _MSC_VER
     68  1.1  christos #    define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline)
     69  1.1  christos #else
     70  1.1  christos #    define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline))
     71  1.1  christos #endif
     72  1.1  christos 
     73  1.1  christos // defined in unordered_dense.cpp
     74  1.1  christos #if !defined(ANKERL_UNORDERED_DENSE_EXPORT)
     75  1.1  christos #    define ANKERL_UNORDERED_DENSE_EXPORT
     76  1.1  christos #endif
     77  1.1  christos 
     78  1.1  christos #if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L
     79  1.1  christos #    error ankerl::unordered_dense requires C++17 or higher
     80  1.1  christos #else
     81  1.1  christos #    include <array>            // for array
     82  1.1  christos #    include <cstdint>          // for uint64_t, uint32_t, uint8_t, UINT64_C
     83  1.1  christos #    include <cstring>          // for size_t, memcpy, memset
     84  1.1  christos #    include <functional>       // for equal_to, hash
     85  1.1  christos #    include <initializer_list> // for initializer_list
     86  1.1  christos #    include <iterator>         // for pair, distance
     87  1.1  christos #    include <limits>           // for numeric_limits
     88  1.1  christos #    include <memory>           // for allocator, allocator_traits, shared_ptr
     89  1.1  christos #    include <optional>         // for optional
     90  1.1  christos #    include <stdexcept>        // for out_of_range
     91  1.1  christos #    include <string>           // for basic_string
     92  1.1  christos #    include <string_view>      // for basic_string_view, hash
     93  1.1  christos #    include <tuple>            // for forward_as_tuple
     94  1.1  christos #    include <type_traits>      // for enable_if_t, declval, conditional_t, ena...
     95  1.1  christos #    include <utility>          // for forward, exchange, pair, as_const, piece...
     96  1.1  christos #    include <vector>           // for vector
     97  1.1  christos #    if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0
     98  1.1  christos #        include <cstdlib> // for abort
     99  1.1  christos #    endif
    100  1.1  christos 
    101  1.1  christos #    if defined(__has_include)
    102  1.1  christos #        if __has_include(<memory_resource>)
    103  1.1  christos #            define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage)
    104  1.1  christos #            include <memory_resource>                  // for polymorphic_allocator
    105  1.1  christos #        elif __has_include(<experimental/memory_resource>)
    106  1.1  christos #            define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage)
    107  1.1  christos #            include <experimental/memory_resource>                   // for polymorphic_allocator
    108  1.1  christos #        endif
    109  1.1  christos #    endif
    110  1.1  christos 
    111  1.1  christos #    if defined(_MSC_VER) && defined(_M_X64)
    112  1.1  christos #        include <intrin.h>
    113  1.1  christos #        pragma intrinsic(_umul128)
    114  1.1  christos #    endif
    115  1.1  christos 
    116  1.1  christos #    if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
    117  1.1  christos #        define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1)   // NOLINT(cppcoreguidelines-macro-usage)
    118  1.1  christos #        define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage)
    119  1.1  christos #    else
    120  1.1  christos #        define ANKERL_UNORDERED_DENSE_LIKELY(x) (x)   // NOLINT(cppcoreguidelines-macro-usage)
    121  1.1  christos #        define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage)
    122  1.1  christos #    endif
    123  1.1  christos 
    124  1.1  christos namespace ankerl::unordered_dense {
    125  1.1  christos inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE {
    126  1.1  christos 
    127  1.1  christos namespace detail {
    128  1.1  christos 
    129  1.1  christos #    if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS()
    130  1.1  christos 
    131  1.1  christos // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
    132  1.1  christos // inlinings more difficult. Throws are also generally the slow path.
    133  1.1  christos [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() {
    134  1.1  christos     throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found");
    135  1.1  christos }
    136  1.1  christos [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() {
    137  1.1  christos     throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size");
    138  1.1  christos }
    139  1.1  christos [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() {
    140  1.1  christos     throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements");
    141  1.1  christos }
    142  1.1  christos 
    143  1.1  christos #    else
    144  1.1  christos 
    145  1.1  christos [[noreturn]] inline void on_error_key_not_found() {
    146  1.1  christos     abort();
    147  1.1  christos }
    148  1.1  christos [[noreturn]] inline void on_error_bucket_overflow() {
    149  1.1  christos     abort();
    150  1.1  christos }
    151  1.1  christos [[noreturn]] inline void on_error_too_many_elements() {
    152  1.1  christos     abort();
    153  1.1  christos }
    154  1.1  christos 
    155  1.1  christos #    endif
    156  1.1  christos 
    157  1.1  christos } // namespace detail
    158  1.1  christos 
    159  1.1  christos // hash ///////////////////////////////////////////////////////////////////////
    160  1.1  christos 
    161  1.1  christos // This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash
    162  1.1  christos // No big-endian support (because different values on different machines don't matter),
    163  1.1  christos // hardcodes seed and the secret, reformats the code, and clang-tidy fixes.
    164  1.1  christos namespace detail::wyhash {
    165  1.1  christos 
    166  1.1  christos inline void mum(uint64_t* a, uint64_t* b) {
    167  1.1  christos #    if defined(__SIZEOF_INT128__)
    168  1.1  christos     __uint128_t r = *a;
    169  1.1  christos     r *= *b;
    170  1.1  christos     *a = static_cast<uint64_t>(r);
    171  1.1  christos     *b = static_cast<uint64_t>(r >> 64U);
    172  1.1  christos #    elif defined(_MSC_VER) && defined(_M_X64)
    173  1.1  christos     *a = _umul128(*a, *b, b);
    174  1.1  christos #    else
    175  1.1  christos     uint64_t ha = *a >> 32U;
    176  1.1  christos     uint64_t hb = *b >> 32U;
    177  1.1  christos     uint64_t la = static_cast<uint32_t>(*a);
    178  1.1  christos     uint64_t lb = static_cast<uint32_t>(*b);
    179  1.1  christos     uint64_t hi{};
    180  1.1  christos     uint64_t lo{};
    181  1.1  christos     uint64_t rh = ha * hb;
    182  1.1  christos     uint64_t rm0 = ha * lb;
    183  1.1  christos     uint64_t rm1 = hb * la;
    184  1.1  christos     uint64_t rl = la * lb;
    185  1.1  christos     uint64_t t = rl + (rm0 << 32U);
    186  1.1  christos     auto c = static_cast<uint64_t>(t < rl);
    187  1.1  christos     lo = t + (rm1 << 32U);
    188  1.1  christos     c += static_cast<uint64_t>(lo < t);
    189  1.1  christos     hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c;
    190  1.1  christos     *a = lo;
    191  1.1  christos     *b = hi;
    192  1.1  christos #    endif
    193  1.1  christos }
    194  1.1  christos 
    195  1.1  christos // multiply and xor mix function, aka MUM
    196  1.1  christos [[nodiscard]] inline auto mix(uint64_t a, uint64_t b) -> uint64_t {
    197  1.1  christos     mum(&a, &b);
    198  1.1  christos     return a ^ b;
    199  1.1  christos }
    200  1.1  christos 
    201  1.1  christos // read functions. WARNING: we don't care about endianness, so results are different on big endian!
    202  1.1  christos [[nodiscard]] inline auto r8(const uint8_t* p) -> uint64_t {
    203  1.1  christos     uint64_t v{};
    204  1.1  christos     std::memcpy(&v, p, 8U);
    205  1.1  christos     return v;
    206  1.1  christos }
    207  1.1  christos 
    208  1.1  christos [[nodiscard]] inline auto r4(const uint8_t* p) -> uint64_t {
    209  1.1  christos     uint32_t v{};
    210  1.1  christos     std::memcpy(&v, p, 4);
    211  1.1  christos     return v;
    212  1.1  christos }
    213  1.1  christos 
    214  1.1  christos // reads 1, 2, or 3 bytes
    215  1.1  christos [[nodiscard]] inline auto r3(const uint8_t* p, size_t k) -> uint64_t {
    216  1.1  christos     return (static_cast<uint64_t>(p[0]) << 16U) | (static_cast<uint64_t>(p[k >> 1U]) << 8U) | p[k - 1];
    217  1.1  christos }
    218  1.1  christos 
    219  1.1  christos [[maybe_unused]] [[nodiscard]] inline auto hash(void const* key, size_t len) -> uint64_t {
    220  1.1  christos     static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f),
    221  1.1  christos                                               UINT64_C(0xe7037ed1a0b428db),
    222  1.1  christos                                               UINT64_C(0x8ebc6af09c88c6e3),
    223  1.1  christos                                               UINT64_C(0x589965cc75374cc3)};
    224  1.1  christos 
    225  1.1  christos     auto const* p = static_cast<uint8_t const*>(key);
    226  1.1  christos     uint64_t seed = secret[0];
    227  1.1  christos     uint64_t a{};
    228  1.1  christos     uint64_t b{};
    229  1.1  christos     if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) {
    230  1.1  christos         if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) {
    231  1.1  christos             a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U));
    232  1.1  christos             b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U));
    233  1.1  christos         } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) {
    234  1.1  christos             a = r3(p, len);
    235  1.1  christos             b = 0;
    236  1.1  christos         } else {
    237  1.1  christos             a = 0;
    238  1.1  christos             b = 0;
    239  1.1  christos         }
    240  1.1  christos     } else {
    241  1.1  christos         size_t i = len;
    242  1.1  christos         if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) {
    243  1.1  christos             uint64_t see1 = seed;
    244  1.1  christos             uint64_t see2 = seed;
    245  1.1  christos             do {
    246  1.1  christos                 seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed);
    247  1.1  christos                 see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1);
    248  1.1  christos                 see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2);
    249  1.1  christos                 p += 48;
    250  1.1  christos                 i -= 48;
    251  1.1  christos             } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48));
    252  1.1  christos             seed ^= see1 ^ see2;
    253  1.1  christos         }
    254  1.1  christos         while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) {
    255  1.1  christos             seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed);
    256  1.1  christos             i -= 16;
    257  1.1  christos             p += 16;
    258  1.1  christos         }
    259  1.1  christos         a = r8(p + i - 16);
    260  1.1  christos         b = r8(p + i - 8);
    261  1.1  christos     }
    262  1.1  christos 
    263  1.1  christos     return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed));
    264  1.1  christos }
    265  1.1  christos 
    266  1.1  christos [[nodiscard]] inline auto hash(uint64_t x) -> uint64_t {
    267  1.1  christos     return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15));
    268  1.1  christos }
    269  1.1  christos 
    270  1.1  christos } // namespace detail::wyhash
    271  1.1  christos 
    272  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <typename T, typename Enable = void>
    273  1.1  christos struct hash {
    274  1.1  christos     auto operator()(T const& obj) const noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>())))
    275  1.1  christos         -> uint64_t {
    276  1.1  christos         return std::hash<T>{}(obj);
    277  1.1  christos     }
    278  1.1  christos };
    279  1.1  christos 
    280  1.1  christos template <typename CharT>
    281  1.1  christos struct hash<std::basic_string<CharT>> {
    282  1.1  christos     using is_avalanching = void;
    283  1.1  christos     auto operator()(std::basic_string<CharT> const& str) const noexcept -> uint64_t {
    284  1.1  christos         return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size());
    285  1.1  christos     }
    286  1.1  christos };
    287  1.1  christos 
    288  1.1  christos template <typename CharT>
    289  1.1  christos struct hash<std::basic_string_view<CharT>> {
    290  1.1  christos     using is_avalanching = void;
    291  1.1  christos     auto operator()(std::basic_string_view<CharT> const& sv) const noexcept -> uint64_t {
    292  1.1  christos         return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size());
    293  1.1  christos     }
    294  1.1  christos };
    295  1.1  christos 
    296  1.1  christos template <class T>
    297  1.1  christos struct hash<T*> {
    298  1.1  christos     using is_avalanching = void;
    299  1.1  christos     auto operator()(T* ptr) const noexcept -> uint64_t {
    300  1.1  christos         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
    301  1.1  christos         return detail::wyhash::hash(reinterpret_cast<uintptr_t>(ptr));
    302  1.1  christos     }
    303  1.1  christos };
    304  1.1  christos 
    305  1.1  christos template <class T>
    306  1.1  christos struct hash<std::unique_ptr<T>> {
    307  1.1  christos     using is_avalanching = void;
    308  1.1  christos     auto operator()(std::unique_ptr<T> const& ptr) const noexcept -> uint64_t {
    309  1.1  christos         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
    310  1.1  christos         return detail::wyhash::hash(reinterpret_cast<uintptr_t>(ptr.get()));
    311  1.1  christos     }
    312  1.1  christos };
    313  1.1  christos 
    314  1.1  christos template <class T>
    315  1.1  christos struct hash<std::shared_ptr<T>> {
    316  1.1  christos     using is_avalanching = void;
    317  1.1  christos     auto operator()(std::shared_ptr<T> const& ptr) const noexcept -> uint64_t {
    318  1.1  christos         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
    319  1.1  christos         return detail::wyhash::hash(reinterpret_cast<uintptr_t>(ptr.get()));
    320  1.1  christos     }
    321  1.1  christos };
    322  1.1  christos 
    323  1.1  christos template <typename Enum>
    324  1.1  christos struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
    325  1.1  christos     using is_avalanching = void;
    326  1.1  christos     auto operator()(Enum e) const noexcept -> uint64_t {
    327  1.1  christos         using underlying = typename std::underlying_type_t<Enum>;
    328  1.1  christos         return detail::wyhash::hash(static_cast<underlying>(e));
    329  1.1  christos     }
    330  1.1  christos };
    331  1.1  christos 
    332  1.1  christos template <typename... Args>
    333  1.1  christos struct tuple_hash_helper {
    334  1.1  christos     // Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest.
    335  1.1  christos     // If it isn't an integral we need to hash it.
    336  1.1  christos     template <typename Arg>
    337  1.1  christos     [[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t {
    338  1.1  christos         if constexpr (std::is_integral_v<Arg> || std::is_enum_v<Arg>) {
    339  1.1  christos             return static_cast<uint64_t>(arg);
    340  1.1  christos         } else {
    341  1.1  christos             return hash<Arg>{}(arg);
    342  1.1  christos         }
    343  1.1  christos     }
    344  1.1  christos 
    345  1.1  christos     [[nodiscard]] static auto mix64(uint64_t state, uint64_t v) -> uint64_t {
    346  1.1  christos         return detail::wyhash::mix(state + v, uint64_t{0x9ddfea08eb382d69});
    347  1.1  christos     }
    348  1.1  christos 
    349  1.1  christos     // Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If
    350  1.1  christos     // not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized
    351  1.1  christos     // away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer.
    352  1.1  christos     template <typename T, std::size_t... Idx>
    353  1.1  christos     [[nodiscard]] static auto calc_hash(T const& t, std::index_sequence<Idx...>) noexcept -> uint64_t {
    354  1.1  christos         auto h = uint64_t{};
    355  1.1  christos         ((h = mix64(h, to64(std::get<Idx>(t)))), ...);
    356  1.1  christos         return h;
    357  1.1  christos     }
    358  1.1  christos };
    359  1.1  christos 
    360  1.1  christos template <typename... Args>
    361  1.1  christos struct hash<std::tuple<Args...>> : tuple_hash_helper<Args...> {
    362  1.1  christos     using is_avalanching = void;
    363  1.1  christos     auto operator()(std::tuple<Args...> const& t) const noexcept -> uint64_t {
    364  1.1  christos         return tuple_hash_helper<Args...>::calc_hash(t, std::index_sequence_for<Args...>{});
    365  1.1  christos     }
    366  1.1  christos };
    367  1.1  christos 
    368  1.1  christos template <typename A, typename B>
    369  1.1  christos struct hash<std::pair<A, B>> : tuple_hash_helper<A, B> {
    370  1.1  christos     using is_avalanching = void;
    371  1.1  christos     auto operator()(std::pair<A, B> const& t) const noexcept -> uint64_t {
    372  1.1  christos         return tuple_hash_helper<A, B>::calc_hash(t, std::index_sequence_for<A, B>{});
    373  1.1  christos     }
    374  1.1  christos };
    375  1.1  christos 
    376  1.1  christos // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
    377  1.1  christos #    define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T)                    \
    378  1.1  christos         template <>                                                      \
    379  1.1  christos         struct hash<T> {                                                 \
    380  1.1  christos             using is_avalanching = void;                                 \
    381  1.1  christos             auto operator()(T const& obj) const noexcept -> uint64_t {   \
    382  1.1  christos                 return detail::wyhash::hash(static_cast<uint64_t>(obj)); \
    383  1.1  christos             }                                                            \
    384  1.1  christos         }
    385  1.1  christos 
    386  1.1  christos #    if defined(__GNUC__) && !defined(__clang__)
    387  1.1  christos #        pragma GCC diagnostic push
    388  1.1  christos #        pragma GCC diagnostic ignored "-Wuseless-cast"
    389  1.1  christos #    endif
    390  1.1  christos // see https://en.cppreference.com/w/cpp/utility/hash
    391  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool);
    392  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char);
    393  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char);
    394  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char);
    395  1.1  christos #    if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L && defined(__cpp_char8_t)
    396  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t);
    397  1.1  christos #    endif
    398  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t);
    399  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t);
    400  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t);
    401  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short);
    402  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short);
    403  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int);
    404  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int);
    405  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long);
    406  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long);
    407  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long);
    408  1.1  christos ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long);
    409  1.1  christos 
    410  1.1  christos #    if defined(__GNUC__) && !defined(__clang__)
    411  1.1  christos #        pragma GCC diagnostic pop
    412  1.1  christos #    endif
    413  1.1  christos 
    414  1.1  christos // bucket_type //////////////////////////////////////////////////////////
    415  1.1  christos 
    416  1.1  christos namespace bucket_type {
    417  1.1  christos 
    418  1.1  christos struct standard {
    419  1.1  christos     static constexpr uint32_t dist_inc = 1U << 8U;             // skip 1 byte fingerprint
    420  1.1  christos     static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint
    421  1.1  christos 
    422  1.1  christos     uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash
    423  1.1  christos     uint32_t m_value_idx;            // index into the m_values vector.
    424  1.1  christos };
    425  1.1  christos 
    426  1.1  christos ANKERL_UNORDERED_DENSE_PACK(struct big {
    427  1.1  christos     static constexpr uint32_t dist_inc = 1U << 8U;             // skip 1 byte fingerprint
    428  1.1  christos     static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint
    429  1.1  christos 
    430  1.1  christos     uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash
    431  1.1  christos     size_t m_value_idx;              // index into the m_values vector.
    432  1.1  christos });
    433  1.1  christos 
    434  1.1  christos } // namespace bucket_type
    435  1.1  christos 
    436  1.1  christos namespace detail {
    437  1.1  christos 
    438  1.1  christos struct nonesuch {};
    439  1.1  christos 
    440  1.1  christos template <class Default, class AlwaysVoid, template <class...> class Op, class... Args>
    441  1.1  christos struct detector {
    442  1.1  christos     using value_t = std::false_type;
    443  1.1  christos     using type = Default;
    444  1.1  christos };
    445  1.1  christos 
    446  1.1  christos template <class Default, template <class...> class Op, class... Args>
    447  1.1  christos struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
    448  1.1  christos     using value_t = std::true_type;
    449  1.1  christos     using type = Op<Args...>;
    450  1.1  christos };
    451  1.1  christos 
    452  1.1  christos template <template <class...> class Op, class... Args>
    453  1.1  christos using is_detected = typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
    454  1.1  christos 
    455  1.1  christos template <template <class...> class Op, class... Args>
    456  1.1  christos constexpr bool is_detected_v = is_detected<Op, Args...>::value;
    457  1.1  christos 
    458  1.1  christos template <typename T>
    459  1.1  christos using detect_avalanching = typename T::is_avalanching;
    460  1.1  christos 
    461  1.1  christos template <typename T>
    462  1.1  christos using detect_is_transparent = typename T::is_transparent;
    463  1.1  christos 
    464  1.1  christos template <typename T>
    465  1.1  christos using detect_iterator = typename T::iterator;
    466  1.1  christos 
    467  1.1  christos template <typename T>
    468  1.1  christos using detect_reserve = decltype(std::declval<T&>().reserve(size_t{}));
    469  1.1  christos 
    470  1.1  christos // enable_if helpers
    471  1.1  christos 
    472  1.1  christos template <typename Mapped>
    473  1.1  christos constexpr bool is_map_v = !std::is_void_v<Mapped>;
    474  1.1  christos 
    475  1.1  christos // clang-format off
    476  1.1  christos template <typename Hash, typename KeyEqual>
    477  1.1  christos constexpr bool is_transparent_v = is_detected_v<detect_is_transparent, Hash> && is_detected_v<detect_is_transparent, KeyEqual>;
    478  1.1  christos // clang-format on
    479  1.1  christos 
    480  1.1  christos template <typename From, typename To1, typename To2>
    481  1.1  christos constexpr bool is_neither_convertible_v = !std::is_convertible_v<From, To1> && !std::is_convertible_v<From, To2>;
    482  1.1  christos 
    483  1.1  christos template <typename T>
    484  1.1  christos constexpr bool has_reserve = is_detected_v<detect_reserve, T>;
    485  1.1  christos 
    486  1.1  christos // base type for map has mapped_type
    487  1.1  christos template <class T>
    488  1.1  christos struct base_table_type_map {
    489  1.1  christos     using mapped_type = T;
    490  1.1  christos };
    491  1.1  christos 
    492  1.1  christos // base type for set doesn't have mapped_type
    493  1.1  christos struct base_table_type_set {};
    494  1.1  christos 
    495  1.1  christos } // namespace detail
    496  1.1  christos 
    497  1.1  christos // Very much like std::deque, but faster for indexing (in most cases). As of now this doesn't implement the full std::vector
    498  1.1  christos // API, but merely what's necessary to work as an underlying container for ankerl::unordered_dense::{map, set}.
    499  1.1  christos // It allocates blocks of equal size and puts them into the m_blocks vector. That means it can grow simply by adding a new
    500  1.1  christos // block to the back of m_blocks, and doesn't double its size like an std::vector. The disadvantage is that memory is not
    501  1.1  christos // linear and thus there is one more indirection necessary for indexing.
    502  1.1  christos template <typename T, typename Allocator = std::allocator<T>, size_t MaxSegmentSizeBytes = 4096>
    503  1.1  christos class segmented_vector {
    504  1.1  christos     template <bool IsConst>
    505  1.1  christos     class iter_t;
    506  1.1  christos 
    507  1.1  christos public:
    508  1.1  christos     using allocator_type = Allocator;
    509  1.1  christos     using pointer = typename std::allocator_traits<allocator_type>::pointer;
    510  1.1  christos     using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
    511  1.1  christos     using difference_type = typename std::allocator_traits<allocator_type>::difference_type;
    512  1.1  christos     using value_type = T;
    513  1.1  christos     using size_type = std::size_t;
    514  1.1  christos     using reference = T&;
    515  1.1  christos     using const_reference = T const&;
    516  1.1  christos     using iterator = iter_t<false>;
    517  1.1  christos     using const_iterator = iter_t<true>;
    518  1.1  christos 
    519  1.1  christos private:
    520  1.1  christos     using vec_alloc = typename std::allocator_traits<Allocator>::template rebind_alloc<pointer>;
    521  1.1  christos     std::vector<pointer, vec_alloc> m_blocks{};
    522  1.1  christos     size_t m_size{};
    523  1.1  christos 
    524  1.1  christos     // Calculates the maximum number for x in  (s << x) <= max_val
    525  1.1  christos     static constexpr auto num_bits_closest(size_t max_val, size_t s) -> size_t {
    526  1.1  christos         auto f = size_t{0};
    527  1.1  christos         while (s << (f + 1) <= max_val) {
    528  1.1  christos             ++f;
    529  1.1  christos         }
    530  1.1  christos         return f;
    531  1.1  christos     }
    532  1.1  christos 
    533  1.1  christos     using self_t = segmented_vector<T, Allocator, MaxSegmentSizeBytes>;
    534  1.1  christos     static constexpr auto num_bits = num_bits_closest(MaxSegmentSizeBytes, sizeof(T));
    535  1.1  christos     static constexpr auto num_elements_in_block = 1U << num_bits;
    536  1.1  christos     static constexpr auto mask = num_elements_in_block - 1U;
    537  1.1  christos 
    538  1.1  christos     /**
    539  1.1  christos      * Iterator class doubles as const_iterator and iterator
    540  1.1  christos      */
    541  1.1  christos     template <bool IsConst>
    542  1.1  christos     class iter_t {
    543  1.1  christos         using ptr_t = typename std::conditional_t<IsConst, segmented_vector::const_pointer const*, segmented_vector::pointer*>;
    544  1.1  christos         ptr_t m_data{};
    545  1.1  christos         size_t m_idx{};
    546  1.1  christos 
    547  1.1  christos         template <bool B>
    548  1.1  christos         friend class iter_t;
    549  1.1  christos 
    550  1.1  christos     public:
    551  1.1  christos         using difference_type = segmented_vector::difference_type;
    552  1.1  christos         using value_type = T;
    553  1.1  christos         using reference = typename std::conditional_t<IsConst, value_type const&, value_type&>;
    554  1.1  christos         using pointer = typename std::conditional_t<IsConst, segmented_vector::const_pointer, segmented_vector::pointer>;
    555  1.1  christos         using iterator_category = std::forward_iterator_tag;
    556  1.1  christos 
    557  1.1  christos         iter_t() noexcept = default;
    558  1.1  christos 
    559  1.1  christos         template <bool OtherIsConst, typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
    560  1.1  christos         // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
    561  1.1  christos         constexpr iter_t(iter_t<OtherIsConst> const& other) noexcept
    562  1.1  christos             : m_data(other.m_data)
    563  1.1  christos             , m_idx(other.m_idx) {}
    564  1.1  christos 
    565  1.1  christos         constexpr iter_t(ptr_t data, size_t idx) noexcept
    566  1.1  christos             : m_data(data)
    567  1.1  christos             , m_idx(idx) {}
    568  1.1  christos 
    569  1.1  christos         template <bool OtherIsConst, typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
    570  1.1  christos         constexpr auto operator=(iter_t<OtherIsConst> const& other) noexcept -> iter_t& {
    571  1.1  christos             m_data = other.m_data;
    572  1.1  christos             m_idx = other.m_idx;
    573  1.1  christos             return *this;
    574  1.1  christos         }
    575  1.1  christos 
    576  1.1  christos         constexpr auto operator++() noexcept -> iter_t& {
    577  1.1  christos             ++m_idx;
    578  1.1  christos             return *this;
    579  1.1  christos         }
    580  1.1  christos 
    581  1.1  christos         constexpr auto operator+(difference_type diff) noexcept -> iter_t {
    582  1.1  christos             return {m_data, static_cast<size_t>(static_cast<difference_type>(m_idx) + diff)};
    583  1.1  christos         }
    584  1.1  christos 
    585  1.1  christos         template <bool OtherIsConst>
    586  1.1  christos         constexpr auto operator-(iter_t<OtherIsConst> const& other) noexcept -> difference_type {
    587  1.1  christos             return static_cast<difference_type>(m_idx) - static_cast<difference_type>(other.m_idx);
    588  1.1  christos         }
    589  1.1  christos 
    590  1.1  christos         constexpr auto operator*() const noexcept -> reference {
    591  1.1  christos             return m_data[m_idx >> num_bits][m_idx & mask];
    592  1.1  christos         }
    593  1.1  christos 
    594  1.1  christos         constexpr auto operator->() const noexcept -> pointer {
    595  1.1  christos             return &m_data[m_idx >> num_bits][m_idx & mask];
    596  1.1  christos         }
    597  1.1  christos 
    598  1.1  christos         template <bool O>
    599  1.1  christos         constexpr auto operator==(iter_t<O> const& o) const noexcept -> bool {
    600  1.1  christos             return m_idx == o.m_idx;
    601  1.1  christos         }
    602  1.1  christos 
    603  1.1  christos         template <bool O>
    604  1.1  christos         constexpr auto operator!=(iter_t<O> const& o) const noexcept -> bool {
    605  1.1  christos             return !(*this == o);
    606  1.1  christos         }
    607  1.1  christos     };
    608  1.1  christos 
    609  1.1  christos     // slow path: need to allocate a new segment every once in a while
    610  1.1  christos     void increase_capacity() {
    611  1.1  christos         auto ba = Allocator(m_blocks.get_allocator());
    612  1.1  christos         pointer block = std::allocator_traits<Allocator>::allocate(ba, num_elements_in_block);
    613  1.1  christos         m_blocks.push_back(block);
    614  1.1  christos     }
    615  1.1  christos 
    616  1.1  christos     // Moves everything from other
    617  1.1  christos     void append_everything_from(segmented_vector&& other) {
    618  1.1  christos         reserve(size() + other.size());
    619  1.1  christos         for (auto&& o : other) {
    620  1.1  christos             emplace_back(std::move(o));
    621  1.1  christos         }
    622  1.1  christos     }
    623  1.1  christos 
    624  1.1  christos     // Copies everything from other
    625  1.1  christos     void append_everything_from(segmented_vector const& other) {
    626  1.1  christos         reserve(size() + other.size());
    627  1.1  christos         for (auto const& o : other) {
    628  1.1  christos             emplace_back(o);
    629  1.1  christos         }
    630  1.1  christos     }
    631  1.1  christos 
    632  1.1  christos     void dealloc() {
    633  1.1  christos         auto ba = Allocator(m_blocks.get_allocator());
    634  1.1  christos         for (auto ptr : m_blocks) {
    635  1.1  christos             std::allocator_traits<Allocator>::deallocate(ba, ptr, num_elements_in_block);
    636  1.1  christos         }
    637  1.1  christos     }
    638  1.1  christos 
    639  1.1  christos     [[nodiscard]] static constexpr auto calc_num_blocks_for_capacity(size_t capacity) {
    640  1.1  christos         return (capacity + num_elements_in_block - 1U) / num_elements_in_block;
    641  1.1  christos     }
    642  1.1  christos 
    643  1.1  christos public:
    644  1.1  christos     segmented_vector() = default;
    645  1.1  christos 
    646  1.1  christos     // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
    647  1.1  christos     segmented_vector(Allocator alloc)
    648  1.1  christos         : m_blocks(vec_alloc(alloc)) {}
    649  1.1  christos 
    650  1.1  christos     segmented_vector(segmented_vector&& other, Allocator alloc)
    651  1.1  christos         : segmented_vector(alloc) {
    652  1.1  christos         *this = std::move(other);
    653  1.1  christos     }
    654  1.1  christos 
    655  1.1  christos     segmented_vector(segmented_vector const& other, Allocator alloc)
    656  1.1  christos         : m_blocks(vec_alloc(alloc)) {
    657  1.1  christos         append_everything_from(other);
    658  1.1  christos     }
    659  1.1  christos 
    660  1.1  christos     segmented_vector(segmented_vector&& other) noexcept
    661  1.1  christos         : segmented_vector(std::move(other), get_allocator()) {}
    662  1.1  christos 
    663  1.1  christos     segmented_vector(segmented_vector const& other) {
    664  1.1  christos         append_everything_from(other);
    665  1.1  christos     }
    666  1.1  christos 
    667  1.1  christos     auto operator=(segmented_vector const& other) -> segmented_vector& {
    668  1.1  christos         if (this == &other) {
    669  1.1  christos             return *this;
    670  1.1  christos         }
    671  1.1  christos         clear();
    672  1.1  christos         append_everything_from(other);
    673  1.1  christos         return *this;
    674  1.1  christos     }
    675  1.1  christos 
    676  1.1  christos     auto operator=(segmented_vector&& other) noexcept -> segmented_vector& {
    677  1.1  christos         clear();
    678  1.1  christos         dealloc();
    679  1.1  christos         if (other.get_allocator() == get_allocator()) {
    680  1.1  christos             m_blocks = std::move(other.m_blocks);
    681  1.1  christos             m_size = std::exchange(other.m_size, {});
    682  1.1  christos         } else {
    683  1.1  christos             // make sure to construct with other's allocator!
    684  1.1  christos             m_blocks = std::vector<pointer, vec_alloc>(vec_alloc(other.get_allocator()));
    685  1.1  christos             append_everything_from(std::move(other));
    686  1.1  christos         }
    687  1.1  christos         return *this;
    688  1.1  christos     }
    689  1.1  christos 
    690  1.1  christos     ~segmented_vector() {
    691  1.1  christos         clear();
    692  1.1  christos         dealloc();
    693  1.1  christos     }
    694  1.1  christos 
    695  1.1  christos     [[nodiscard]] constexpr auto size() const -> size_t {
    696  1.1  christos         return m_size;
    697  1.1  christos     }
    698  1.1  christos 
    699  1.1  christos     [[nodiscard]] constexpr auto capacity() const -> size_t {
    700  1.1  christos         return m_blocks.size() * num_elements_in_block;
    701  1.1  christos     }
    702  1.1  christos 
    703  1.1  christos     // Indexing is highly performance critical
    704  1.1  christos     [[nodiscard]] constexpr auto operator[](size_t i) const noexcept -> T const& {
    705  1.1  christos         return m_blocks[i >> num_bits][i & mask];
    706  1.1  christos     }
    707  1.1  christos 
    708  1.1  christos     [[nodiscard]] constexpr auto operator[](size_t i) noexcept -> T& {
    709  1.1  christos         return m_blocks[i >> num_bits][i & mask];
    710  1.1  christos     }
    711  1.1  christos 
    712  1.1  christos     [[nodiscard]] constexpr auto begin() -> iterator {
    713  1.1  christos         return {m_blocks.data(), 0U};
    714  1.1  christos     }
    715  1.1  christos     [[nodiscard]] constexpr auto begin() const -> const_iterator {
    716  1.1  christos         return {m_blocks.data(), 0U};
    717  1.1  christos     }
    718  1.1  christos     [[nodiscard]] constexpr auto cbegin() const -> const_iterator {
    719  1.1  christos         return {m_blocks.data(), 0U};
    720  1.1  christos     }
    721  1.1  christos 
    722  1.1  christos     [[nodiscard]] constexpr auto end() -> iterator {
    723  1.1  christos         return {m_blocks.data(), m_size};
    724  1.1  christos     }
    725  1.1  christos     [[nodiscard]] constexpr auto end() const -> const_iterator {
    726  1.1  christos         return {m_blocks.data(), m_size};
    727  1.1  christos     }
    728  1.1  christos     [[nodiscard]] constexpr auto cend() const -> const_iterator {
    729  1.1  christos         return {m_blocks.data(), m_size};
    730  1.1  christos     }
    731  1.1  christos 
    732  1.1  christos     [[nodiscard]] constexpr auto back() -> reference {
    733  1.1  christos         return operator[](m_size - 1);
    734  1.1  christos     }
    735  1.1  christos     [[nodiscard]] constexpr auto back() const -> const_reference {
    736  1.1  christos         return operator[](m_size - 1);
    737  1.1  christos     }
    738  1.1  christos 
    739  1.1  christos     void pop_back() {
    740  1.1  christos         back().~T();
    741  1.1  christos         --m_size;
    742  1.1  christos     }
    743  1.1  christos 
    744  1.1  christos     [[nodiscard]] auto empty() const {
    745  1.1  christos         return 0 == m_size;
    746  1.1  christos     }
    747  1.1  christos 
    748  1.1  christos     void reserve(size_t new_capacity) {
    749  1.1  christos         m_blocks.reserve(calc_num_blocks_for_capacity(new_capacity));
    750  1.1  christos         while (new_capacity > capacity()) {
    751  1.1  christos             increase_capacity();
    752  1.1  christos         }
    753  1.1  christos     }
    754  1.1  christos 
    755  1.1  christos     [[nodiscard]] auto get_allocator() const -> allocator_type {
    756  1.1  christos         return allocator_type{m_blocks.get_allocator()};
    757  1.1  christos     }
    758  1.1  christos 
    759  1.1  christos     template <class... Args>
    760  1.1  christos     auto emplace_back(Args&&... args) -> reference {
    761  1.1  christos         if (m_size == capacity()) {
    762  1.1  christos             increase_capacity();
    763  1.1  christos         }
    764  1.1  christos         auto* ptr = static_cast<void*>(&operator[](m_size));
    765  1.1  christos         auto& ref = *new (ptr) T(std::forward<Args>(args)...);
    766  1.1  christos         ++m_size;
    767  1.1  christos         return ref;
    768  1.1  christos     }
    769  1.1  christos 
    770  1.1  christos     void clear() {
    771  1.1  christos         if constexpr (!std::is_trivially_destructible_v<T>) {
    772  1.1  christos             for (size_t i = 0, s = size(); i < s; ++i) {
    773  1.1  christos                 operator[](i).~T();
    774  1.1  christos             }
    775  1.1  christos         }
    776  1.1  christos         m_size = 0;
    777  1.1  christos     }
    778  1.1  christos 
    779  1.1  christos     void shrink_to_fit() {
    780  1.1  christos         auto ba = Allocator(m_blocks.get_allocator());
    781  1.1  christos         auto num_blocks_required = calc_num_blocks_for_capacity(m_size);
    782  1.1  christos         while (m_blocks.size() > num_blocks_required) {
    783  1.1  christos             std::allocator_traits<Allocator>::deallocate(ba, m_blocks.back(), num_elements_in_block);
    784  1.1  christos             m_blocks.pop_back();
    785  1.1  christos         }
    786  1.1  christos         m_blocks.shrink_to_fit();
    787  1.1  christos     }
    788  1.1  christos };
    789  1.1  christos 
    790  1.1  christos namespace detail {
    791  1.1  christos 
    792  1.1  christos // This is it, the table. Doubles as map and set, and uses `void` for T when its used as a set.
    793  1.1  christos template <class Key,
    794  1.1  christos           class T, // when void, treat it as a set.
    795  1.1  christos           class Hash,
    796  1.1  christos           class KeyEqual,
    797  1.1  christos           class AllocatorOrContainer,
    798  1.1  christos           class Bucket,
    799  1.1  christos           bool IsSegmented>
    800  1.1  christos class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, base_table_type_set> {
    801  1.1  christos     using underlying_value_type = typename std::conditional_t<is_map_v<T>, std::pair<Key, T>, Key>;
    802  1.1  christos     using underlying_container_type = std::conditional_t<IsSegmented,
    803  1.1  christos                                                          segmented_vector<underlying_value_type, AllocatorOrContainer>,
    804  1.1  christos                                                          std::vector<underlying_value_type, AllocatorOrContainer>>;
    805  1.1  christos 
    806  1.1  christos public:
    807  1.1  christos     using value_container_type = std::
    808  1.1  christos         conditional_t<is_detected_v<detect_iterator, AllocatorOrContainer>, AllocatorOrContainer, underlying_container_type>;
    809  1.1  christos 
    810  1.1  christos private:
    811  1.1  christos     using bucket_alloc =
    812  1.1  christos         typename std::allocator_traits<typename value_container_type::allocator_type>::template rebind_alloc<Bucket>;
    813  1.1  christos     using bucket_alloc_traits = std::allocator_traits<bucket_alloc>;
    814  1.1  christos 
    815  1.1  christos     static constexpr uint8_t initial_shifts = 64 - 2; // 2^(64-m_shift) number of buckets
    816  1.1  christos     static constexpr float default_max_load_factor = 0.8F;
    817  1.1  christos 
    818  1.1  christos public:
    819  1.1  christos     using key_type = Key;
    820  1.1  christos     using value_type = typename value_container_type::value_type;
    821  1.1  christos     using size_type = typename value_container_type::size_type;
    822  1.1  christos     using difference_type = typename value_container_type::difference_type;
    823  1.1  christos     using hasher = Hash;
    824  1.1  christos     using key_equal = KeyEqual;
    825  1.1  christos     using allocator_type = typename value_container_type::allocator_type;
    826  1.1  christos     using reference = typename value_container_type::reference;
    827  1.1  christos     using const_reference = typename value_container_type::const_reference;
    828  1.1  christos     using pointer = typename value_container_type::pointer;
    829  1.1  christos     using const_pointer = typename value_container_type::const_pointer;
    830  1.1  christos     using const_iterator = typename value_container_type::const_iterator;
    831  1.1  christos     using iterator = std::conditional_t<is_map_v<T>, typename value_container_type::iterator, const_iterator>;
    832  1.1  christos     using bucket_type = Bucket;
    833  1.1  christos 
    834  1.1  christos private:
    835  1.1  christos     using value_idx_type = decltype(Bucket::m_value_idx);
    836  1.1  christos     using dist_and_fingerprint_type = decltype(Bucket::m_dist_and_fingerprint);
    837  1.1  christos 
    838  1.1  christos     static_assert(std::is_trivially_destructible_v<Bucket>, "assert there's no need to call destructor / std::destroy");
    839  1.1  christos     static_assert(std::is_trivially_copyable_v<Bucket>, "assert we can just memset / memcpy");
    840  1.1  christos 
    841  1.1  christos     value_container_type m_values{}; // Contains all the key-value pairs in one densely stored container. No holes.
    842  1.1  christos     using bucket_pointer = typename std::allocator_traits<bucket_alloc>::pointer;
    843  1.1  christos     bucket_pointer m_buckets{};
    844  1.1  christos     size_t m_num_buckets = 0;
    845  1.1  christos     size_t m_max_bucket_capacity = 0;
    846  1.1  christos     float m_max_load_factor = default_max_load_factor;
    847  1.1  christos     Hash m_hash{};
    848  1.1  christos     KeyEqual m_equal{};
    849  1.1  christos     uint8_t m_shifts = initial_shifts;
    850  1.1  christos 
    851  1.1  christos     [[nodiscard]] auto next(value_idx_type bucket_idx) const -> value_idx_type {
    852  1.1  christos         return ANKERL_UNORDERED_DENSE_UNLIKELY(bucket_idx + 1U == m_num_buckets)
    853  1.1  christos                    ? 0
    854  1.1  christos                    : static_cast<value_idx_type>(bucket_idx + 1U);
    855  1.1  christos     }
    856  1.1  christos 
    857  1.1  christos     // Helper to access bucket through pointer types
    858  1.1  christos     [[nodiscard]] static constexpr auto at(bucket_pointer bucket_ptr, size_t offset) -> Bucket& {
    859  1.1  christos         return *(bucket_ptr + static_cast<typename std::allocator_traits<bucket_alloc>::difference_type>(offset));
    860  1.1  christos     }
    861  1.1  christos 
    862  1.1  christos     // use the dist_inc and dist_dec functions so that uint16_t types work without warning
    863  1.1  christos     [[nodiscard]] static constexpr auto dist_inc(dist_and_fingerprint_type x) -> dist_and_fingerprint_type {
    864  1.1  christos         return static_cast<dist_and_fingerprint_type>(x + Bucket::dist_inc);
    865  1.1  christos     }
    866  1.1  christos 
    867  1.1  christos     [[nodiscard]] static constexpr auto dist_dec(dist_and_fingerprint_type x) -> dist_and_fingerprint_type {
    868  1.1  christos         return static_cast<dist_and_fingerprint_type>(x - Bucket::dist_inc);
    869  1.1  christos     }
    870  1.1  christos 
    871  1.1  christos     // The goal of mixed_hash is to always produce a high quality 64bit hash.
    872  1.1  christos     template <typename K>
    873  1.1  christos     [[nodiscard]] constexpr auto mixed_hash(K const& key) const -> uint64_t {
    874  1.1  christos         if constexpr (is_detected_v<detect_avalanching, Hash>) {
    875  1.1  christos             // we know that the hash is good because is_avalanching.
    876  1.1  christos             if constexpr (sizeof(decltype(m_hash(key))) < sizeof(uint64_t)) {
    877  1.1  christos                 // 32bit hash and is_avalanching => multiply with a constant to avalanche bits upwards
    878  1.1  christos                 return m_hash(key) * UINT64_C(0x9ddfea08eb382d69);
    879  1.1  christos             } else {
    880  1.1  christos                 // 64bit and is_avalanching => only use the hash itself.
    881  1.1  christos                 return m_hash(key);
    882  1.1  christos             }
    883  1.1  christos         } else {
    884  1.1  christos             // not is_avalanching => apply wyhash
    885  1.1  christos             return wyhash::hash(m_hash(key));
    886  1.1  christos         }
    887  1.1  christos     }
    888  1.1  christos 
    889  1.1  christos     [[nodiscard]] constexpr auto dist_and_fingerprint_from_hash(uint64_t hash) const -> dist_and_fingerprint_type {
    890  1.1  christos         return Bucket::dist_inc | (static_cast<dist_and_fingerprint_type>(hash) & Bucket::fingerprint_mask);
    891  1.1  christos     }
    892  1.1  christos 
    893  1.1  christos     [[nodiscard]] constexpr auto bucket_idx_from_hash(uint64_t hash) const -> value_idx_type {
    894  1.1  christos         return static_cast<value_idx_type>(hash >> m_shifts);
    895  1.1  christos     }
    896  1.1  christos 
    897  1.1  christos     [[nodiscard]] static constexpr auto get_key(value_type const& vt) -> key_type const& {
    898  1.1  christos         if constexpr (is_map_v<T>) {
    899  1.1  christos             return vt.first;
    900  1.1  christos         } else {
    901  1.1  christos             return vt;
    902  1.1  christos         }
    903  1.1  christos     }
    904  1.1  christos 
    905  1.1  christos     template <typename K>
    906  1.1  christos     [[nodiscard]] auto next_while_less(K const& key) const -> Bucket {
    907  1.1  christos         auto hash = mixed_hash(key);
    908  1.1  christos         auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
    909  1.1  christos         auto bucket_idx = bucket_idx_from_hash(hash);
    910  1.1  christos 
    911  1.1  christos         while (dist_and_fingerprint < at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
    912  1.1  christos             dist_and_fingerprint = dist_inc(dist_and_fingerprint);
    913  1.1  christos             bucket_idx = next(bucket_idx);
    914  1.1  christos         }
    915  1.1  christos         return {dist_and_fingerprint, bucket_idx};
    916  1.1  christos     }
    917  1.1  christos 
    918  1.1  christos     void place_and_shift_up(Bucket bucket, value_idx_type place) {
    919  1.1  christos         while (0 != at(m_buckets, place).m_dist_and_fingerprint) {
    920  1.1  christos             bucket = std::exchange(at(m_buckets, place), bucket);
    921  1.1  christos             bucket.m_dist_and_fingerprint = dist_inc(bucket.m_dist_and_fingerprint);
    922  1.1  christos             place = next(place);
    923  1.1  christos         }
    924  1.1  christos         at(m_buckets, place) = bucket;
    925  1.1  christos     }
    926  1.1  christos 
    927  1.1  christos     [[nodiscard]] static constexpr auto calc_num_buckets(uint8_t shifts) -> size_t {
    928  1.1  christos         return (std::min)(max_bucket_count(), size_t{1} << (64U - shifts));
    929  1.1  christos     }
    930  1.1  christos 
    931  1.1  christos     [[nodiscard]] constexpr auto calc_shifts_for_size(size_t s) const -> uint8_t {
    932  1.1  christos         auto shifts = initial_shifts;
    933  1.1  christos         while (shifts > 0 && static_cast<size_t>(static_cast<float>(calc_num_buckets(shifts)) * max_load_factor()) < s) {
    934  1.1  christos             --shifts;
    935  1.1  christos         }
    936  1.1  christos         return shifts;
    937  1.1  christos     }
    938  1.1  christos 
    939  1.1  christos     // assumes m_values has data, m_buckets=m_buckets_end=nullptr, m_shifts is INITIAL_SHIFTS
    940  1.1  christos     void copy_buckets(table const& other) {
    941  1.1  christos         // assumes m_values has already the correct data copied over.
    942  1.1  christos         if (empty()) {
    943  1.1  christos             // when empty, at least allocate an initial buckets and clear them.
    944  1.1  christos             allocate_buckets_from_shift();
    945  1.1  christos             clear_buckets();
    946  1.1  christos         } else {
    947  1.1  christos             m_shifts = other.m_shifts;
    948  1.1  christos             allocate_buckets_from_shift();
    949  1.1  christos             std::memcpy(m_buckets, other.m_buckets, sizeof(Bucket) * bucket_count());
    950  1.1  christos         }
    951  1.1  christos     }
    952  1.1  christos 
    953  1.1  christos     /**
    954  1.1  christos      * True when no element can be added any more without increasing the size
    955  1.1  christos      */
    956  1.1  christos     [[nodiscard]] auto is_full() const -> bool {
    957  1.1  christos         return size() > m_max_bucket_capacity;
    958  1.1  christos     }
    959  1.1  christos 
    960  1.1  christos     void deallocate_buckets() {
    961  1.1  christos         auto ba = bucket_alloc(m_values.get_allocator());
    962  1.1  christos         if (nullptr != m_buckets) {
    963  1.1  christos             bucket_alloc_traits::deallocate(ba, m_buckets, bucket_count());
    964  1.1  christos             m_buckets = nullptr;
    965  1.1  christos         }
    966  1.1  christos         m_num_buckets = 0;
    967  1.1  christos         m_max_bucket_capacity = 0;
    968  1.1  christos     }
    969  1.1  christos 
    970  1.1  christos     void allocate_buckets_from_shift() {
    971  1.1  christos         auto ba = bucket_alloc(m_values.get_allocator());
    972  1.1  christos         m_num_buckets = calc_num_buckets(m_shifts);
    973  1.1  christos         m_buckets = bucket_alloc_traits::allocate(ba, m_num_buckets);
    974  1.1  christos         if (m_num_buckets == max_bucket_count()) {
    975  1.1  christos             // reached the maximum, make sure we can use each bucket
    976  1.1  christos             m_max_bucket_capacity = max_bucket_count();
    977  1.1  christos         } else {
    978  1.1  christos             m_max_bucket_capacity = static_cast<value_idx_type>(static_cast<float>(m_num_buckets) * max_load_factor());
    979  1.1  christos         }
    980  1.1  christos     }
    981  1.1  christos 
    982  1.1  christos     void clear_buckets() {
    983  1.1  christos         if (m_buckets != nullptr) {
    984  1.1  christos             std::memset(&*m_buckets, 0, sizeof(Bucket) * bucket_count());
    985  1.1  christos         }
    986  1.1  christos     }
    987  1.1  christos 
    988  1.1  christos     void clear_and_fill_buckets_from_values() {
    989  1.1  christos         clear_buckets();
    990  1.1  christos         for (value_idx_type value_idx = 0, end_idx = static_cast<value_idx_type>(m_values.size()); value_idx < end_idx;
    991  1.1  christos              ++value_idx) {
    992  1.1  christos             auto const& key = get_key(m_values[value_idx]);
    993  1.1  christos             auto [dist_and_fingerprint, bucket] = next_while_less(key);
    994  1.1  christos 
    995  1.1  christos             // we know for certain that key has not yet been inserted, so no need to check it.
    996  1.1  christos             place_and_shift_up({dist_and_fingerprint, value_idx}, bucket);
    997  1.1  christos         }
    998  1.1  christos     }
    999  1.1  christos 
   1000  1.1  christos     void increase_size() {
   1001  1.1  christos         if (m_max_bucket_capacity == max_bucket_count()) {
   1002  1.1  christos             // remove the value again, we can't add it!
   1003  1.1  christos             m_values.pop_back();
   1004  1.1  christos             on_error_bucket_overflow();
   1005  1.1  christos         }
   1006  1.1  christos         --m_shifts;
   1007  1.1  christos         deallocate_buckets();
   1008  1.1  christos         allocate_buckets_from_shift();
   1009  1.1  christos         clear_and_fill_buckets_from_values();
   1010  1.1  christos     }
   1011  1.1  christos 
   1012  1.1  christos     template <typename Op>
   1013  1.1  christos     void do_erase(value_idx_type bucket_idx, Op handle_erased_value) {
   1014  1.1  christos         auto const value_idx_to_remove = at(m_buckets, bucket_idx).m_value_idx;
   1015  1.1  christos 
   1016  1.1  christos         // shift down until either empty or an element with correct spot is found
   1017  1.1  christos         auto next_bucket_idx = next(bucket_idx);
   1018  1.1  christos         while (at(m_buckets, next_bucket_idx).m_dist_and_fingerprint >= Bucket::dist_inc * 2) {
   1019  1.1  christos             at(m_buckets, bucket_idx) = {dist_dec(at(m_buckets, next_bucket_idx).m_dist_and_fingerprint),
   1020  1.1  christos                                          at(m_buckets, next_bucket_idx).m_value_idx};
   1021  1.1  christos             bucket_idx = std::exchange(next_bucket_idx, next(next_bucket_idx));
   1022  1.1  christos         }
   1023  1.1  christos         at(m_buckets, bucket_idx) = {};
   1024  1.1  christos         handle_erased_value(std::move(m_values[value_idx_to_remove]));
   1025  1.1  christos 
   1026  1.1  christos         // update m_values
   1027  1.1  christos         if (value_idx_to_remove != m_values.size() - 1) {
   1028  1.1  christos             // no luck, we'll have to replace the value with the last one and update the index accordingly
   1029  1.1  christos             auto& val = m_values[value_idx_to_remove];
   1030  1.1  christos             val = std::move(m_values.back());
   1031  1.1  christos 
   1032  1.1  christos             // update the values_idx of the moved entry. No need to play the info game, just look until we find the values_idx
   1033  1.1  christos             auto mh = mixed_hash(get_key(val));
   1034  1.1  christos             bucket_idx = bucket_idx_from_hash(mh);
   1035  1.1  christos 
   1036  1.1  christos             auto const values_idx_back = static_cast<value_idx_type>(m_values.size() - 1);
   1037  1.1  christos             while (values_idx_back != at(m_buckets, bucket_idx).m_value_idx) {
   1038  1.1  christos                 bucket_idx = next(bucket_idx);
   1039  1.1  christos             }
   1040  1.1  christos             at(m_buckets, bucket_idx).m_value_idx = value_idx_to_remove;
   1041  1.1  christos         }
   1042  1.1  christos         m_values.pop_back();
   1043  1.1  christos     }
   1044  1.1  christos 
   1045  1.1  christos     template <typename K, typename Op>
   1046  1.1  christos     auto do_erase_key(K&& key, Op handle_erased_value) -> size_t {
   1047  1.1  christos         if (empty()) {
   1048  1.1  christos             return 0;
   1049  1.1  christos         }
   1050  1.1  christos 
   1051  1.1  christos         auto [dist_and_fingerprint, bucket_idx] = next_while_less(key);
   1052  1.1  christos 
   1053  1.1  christos         while (dist_and_fingerprint == at(m_buckets, bucket_idx).m_dist_and_fingerprint &&
   1054  1.1  christos                !m_equal(key, get_key(m_values[at(m_buckets, bucket_idx).m_value_idx]))) {
   1055  1.1  christos             dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1056  1.1  christos             bucket_idx = next(bucket_idx);
   1057  1.1  christos         }
   1058  1.1  christos 
   1059  1.1  christos         if (dist_and_fingerprint != at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
   1060  1.1  christos             return 0;
   1061  1.1  christos         }
   1062  1.1  christos         do_erase(bucket_idx, handle_erased_value);
   1063  1.1  christos         return 1;
   1064  1.1  christos     }
   1065  1.1  christos 
   1066  1.1  christos     template <class K, class M>
   1067  1.1  christos     auto do_insert_or_assign(K&& key, M&& mapped) -> std::pair<iterator, bool> {
   1068  1.1  christos         auto it_isinserted = try_emplace(std::forward<K>(key), std::forward<M>(mapped));
   1069  1.1  christos         if (!it_isinserted.second) {
   1070  1.1  christos             it_isinserted.first->second = std::forward<M>(mapped);
   1071  1.1  christos         }
   1072  1.1  christos         return it_isinserted;
   1073  1.1  christos     }
   1074  1.1  christos 
   1075  1.1  christos     template <typename... Args>
   1076  1.1  christos     auto do_place_element(dist_and_fingerprint_type dist_and_fingerprint, value_idx_type bucket_idx, Args&&... args)
   1077  1.1  christos         -> std::pair<iterator, bool> {
   1078  1.1  christos 
   1079  1.1  christos         // emplace the new value. If that throws an exception, no harm done; index is still in a valid state
   1080  1.1  christos         m_values.emplace_back(std::forward<Args>(args)...);
   1081  1.1  christos 
   1082  1.1  christos         auto value_idx = static_cast<value_idx_type>(m_values.size() - 1);
   1083  1.1  christos         if (ANKERL_UNORDERED_DENSE_UNLIKELY(is_full())) {
   1084  1.1  christos             increase_size();
   1085  1.1  christos         } else {
   1086  1.1  christos             place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
   1087  1.1  christos         }
   1088  1.1  christos 
   1089  1.1  christos         // place element and shift up until we find an empty spot
   1090  1.1  christos         return {begin() + static_cast<difference_type>(value_idx), true};
   1091  1.1  christos     }
   1092  1.1  christos 
   1093  1.1  christos     template <typename K, typename... Args>
   1094  1.1  christos     auto do_try_emplace(K&& key, Args&&... args) -> std::pair<iterator, bool> {
   1095  1.1  christos         auto hash = mixed_hash(key);
   1096  1.1  christos         auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
   1097  1.1  christos         auto bucket_idx = bucket_idx_from_hash(hash);
   1098  1.1  christos 
   1099  1.1  christos         while (true) {
   1100  1.1  christos             auto* bucket = &at(m_buckets, bucket_idx);
   1101  1.1  christos             if (dist_and_fingerprint == bucket->m_dist_and_fingerprint) {
   1102  1.1  christos                 if (m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
   1103  1.1  christos                     return {begin() + static_cast<difference_type>(bucket->m_value_idx), false};
   1104  1.1  christos                 }
   1105  1.1  christos             } else if (dist_and_fingerprint > bucket->m_dist_and_fingerprint) {
   1106  1.1  christos                 return do_place_element(dist_and_fingerprint,
   1107  1.1  christos                                         bucket_idx,
   1108  1.1  christos                                         std::piecewise_construct,
   1109  1.1  christos                                         std::forward_as_tuple(std::forward<K>(key)),
   1110  1.1  christos                                         std::forward_as_tuple(std::forward<Args>(args)...));
   1111  1.1  christos             }
   1112  1.1  christos             dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1113  1.1  christos             bucket_idx = next(bucket_idx);
   1114  1.1  christos         }
   1115  1.1  christos     }
   1116  1.1  christos 
   1117  1.1  christos     template <typename K>
   1118  1.1  christos     auto do_find(K const& key) -> iterator {
   1119  1.1  christos         if (ANKERL_UNORDERED_DENSE_UNLIKELY(empty())) {
   1120  1.1  christos             return end();
   1121  1.1  christos         }
   1122  1.1  christos 
   1123  1.1  christos         auto mh = mixed_hash(key);
   1124  1.1  christos         auto dist_and_fingerprint = dist_and_fingerprint_from_hash(mh);
   1125  1.1  christos         auto bucket_idx = bucket_idx_from_hash(mh);
   1126  1.1  christos         auto* bucket = &at(m_buckets, bucket_idx);
   1127  1.1  christos 
   1128  1.1  christos         // unrolled loop. *Always* check a few directly, then enter the loop. This is faster.
   1129  1.1  christos         if (dist_and_fingerprint == bucket->m_dist_and_fingerprint && m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
   1130  1.1  christos             return begin() + static_cast<difference_type>(bucket->m_value_idx);
   1131  1.1  christos         }
   1132  1.1  christos         dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1133  1.1  christos         bucket_idx = next(bucket_idx);
   1134  1.1  christos         bucket = &at(m_buckets, bucket_idx);
   1135  1.1  christos 
   1136  1.1  christos         if (dist_and_fingerprint == bucket->m_dist_and_fingerprint && m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
   1137  1.1  christos             return begin() + static_cast<difference_type>(bucket->m_value_idx);
   1138  1.1  christos         }
   1139  1.1  christos         dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1140  1.1  christos         bucket_idx = next(bucket_idx);
   1141  1.1  christos         bucket = &at(m_buckets, bucket_idx);
   1142  1.1  christos 
   1143  1.1  christos         while (true) {
   1144  1.1  christos             if (dist_and_fingerprint == bucket->m_dist_and_fingerprint) {
   1145  1.1  christos                 if (m_equal(key, get_key(m_values[bucket->m_value_idx]))) {
   1146  1.1  christos                     return begin() + static_cast<difference_type>(bucket->m_value_idx);
   1147  1.1  christos                 }
   1148  1.1  christos             } else if (dist_and_fingerprint > bucket->m_dist_and_fingerprint) {
   1149  1.1  christos                 return end();
   1150  1.1  christos             }
   1151  1.1  christos             dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1152  1.1  christos             bucket_idx = next(bucket_idx);
   1153  1.1  christos             bucket = &at(m_buckets, bucket_idx);
   1154  1.1  christos         }
   1155  1.1  christos     }
   1156  1.1  christos 
   1157  1.1  christos     template <typename K>
   1158  1.1  christos     auto do_find(K const& key) const -> const_iterator {
   1159  1.1  christos         return const_cast<table*>(this)->do_find(key); // NOLINT(cppcoreguidelines-pro-type-const-cast)
   1160  1.1  christos     }
   1161  1.1  christos 
   1162  1.1  christos     template <typename K, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1163  1.1  christos     auto do_at(K const& key) -> Q& {
   1164  1.1  christos         if (auto it = find(key); ANKERL_UNORDERED_DENSE_LIKELY(end() != it)) {
   1165  1.1  christos             return it->second;
   1166  1.1  christos         }
   1167  1.1  christos         on_error_key_not_found();
   1168  1.1  christos     }
   1169  1.1  christos 
   1170  1.1  christos     template <typename K, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1171  1.1  christos     auto do_at(K const& key) const -> Q const& {
   1172  1.1  christos         return const_cast<table*>(this)->at(key); // NOLINT(cppcoreguidelines-pro-type-const-cast)
   1173  1.1  christos     }
   1174  1.1  christos 
   1175  1.1  christos public:
   1176  1.1  christos     explicit table(size_t bucket_count,
   1177  1.1  christos                    Hash const& hash = Hash(),
   1178  1.1  christos                    KeyEqual const& equal = KeyEqual(),
   1179  1.1  christos                    allocator_type const& alloc_or_container = allocator_type())
   1180  1.1  christos         : m_values(alloc_or_container)
   1181  1.1  christos         , m_hash(hash)
   1182  1.1  christos         , m_equal(equal) {
   1183  1.1  christos         if (0 != bucket_count) {
   1184  1.1  christos             reserve(bucket_count);
   1185  1.1  christos         } else {
   1186  1.1  christos             allocate_buckets_from_shift();
   1187  1.1  christos             clear_buckets();
   1188  1.1  christos         }
   1189  1.1  christos     }
   1190  1.1  christos 
   1191  1.1  christos     table()
   1192  1.1  christos         : table(0) {}
   1193  1.1  christos 
   1194  1.1  christos     table(size_t bucket_count, allocator_type const& alloc)
   1195  1.1  christos         : table(bucket_count, Hash(), KeyEqual(), alloc) {}
   1196  1.1  christos 
   1197  1.1  christos     table(size_t bucket_count, Hash const& hash, allocator_type const& alloc)
   1198  1.1  christos         : table(bucket_count, hash, KeyEqual(), alloc) {}
   1199  1.1  christos 
   1200  1.1  christos     explicit table(allocator_type const& alloc)
   1201  1.1  christos         : table(0, Hash(), KeyEqual(), alloc) {}
   1202  1.1  christos 
   1203  1.1  christos     template <class InputIt>
   1204  1.1  christos     table(InputIt first,
   1205  1.1  christos           InputIt last,
   1206  1.1  christos           size_type bucket_count = 0,
   1207  1.1  christos           Hash const& hash = Hash(),
   1208  1.1  christos           KeyEqual const& equal = KeyEqual(),
   1209  1.1  christos           allocator_type const& alloc = allocator_type())
   1210  1.1  christos         : table(bucket_count, hash, equal, alloc) {
   1211  1.1  christos         insert(first, last);
   1212  1.1  christos     }
   1213  1.1  christos 
   1214  1.1  christos     template <class InputIt>
   1215  1.1  christos     table(InputIt first, InputIt last, size_type bucket_count, allocator_type const& alloc)
   1216  1.1  christos         : table(first, last, bucket_count, Hash(), KeyEqual(), alloc) {}
   1217  1.1  christos 
   1218  1.1  christos     template <class InputIt>
   1219  1.1  christos     table(InputIt first, InputIt last, size_type bucket_count, Hash const& hash, allocator_type const& alloc)
   1220  1.1  christos         : table(first, last, bucket_count, hash, KeyEqual(), alloc) {}
   1221  1.1  christos 
   1222  1.1  christos     table(table const& other)
   1223  1.1  christos         : table(other, other.m_values.get_allocator()) {}
   1224  1.1  christos 
   1225  1.1  christos     table(table const& other, allocator_type const& alloc)
   1226  1.1  christos         : m_values(other.m_values, alloc)
   1227  1.1  christos         , m_max_load_factor(other.m_max_load_factor)
   1228  1.1  christos         , m_hash(other.m_hash)
   1229  1.1  christos         , m_equal(other.m_equal) {
   1230  1.1  christos         copy_buckets(other);
   1231  1.1  christos     }
   1232  1.1  christos 
   1233  1.1  christos     table(table&& other) noexcept
   1234  1.1  christos         : table(std::move(other), other.m_values.get_allocator()) {}
   1235  1.1  christos 
   1236  1.1  christos     table(table&& other, allocator_type const& alloc) noexcept
   1237  1.1  christos         : m_values(alloc) {
   1238  1.1  christos         *this = std::move(other);
   1239  1.1  christos     }
   1240  1.1  christos 
   1241  1.1  christos     table(std::initializer_list<value_type> ilist,
   1242  1.1  christos           size_t bucket_count = 0,
   1243  1.1  christos           Hash const& hash = Hash(),
   1244  1.1  christos           KeyEqual const& equal = KeyEqual(),
   1245  1.1  christos           allocator_type const& alloc = allocator_type())
   1246  1.1  christos         : table(bucket_count, hash, equal, alloc) {
   1247  1.1  christos         insert(ilist);
   1248  1.1  christos     }
   1249  1.1  christos 
   1250  1.1  christos     table(std::initializer_list<value_type> ilist, size_type bucket_count, allocator_type const& alloc)
   1251  1.1  christos         : table(ilist, bucket_count, Hash(), KeyEqual(), alloc) {}
   1252  1.1  christos 
   1253  1.1  christos     table(std::initializer_list<value_type> init, size_type bucket_count, Hash const& hash, allocator_type const& alloc)
   1254  1.1  christos         : table(init, bucket_count, hash, KeyEqual(), alloc) {}
   1255  1.1  christos 
   1256  1.1  christos     ~table() {
   1257  1.1  christos         if (nullptr != m_buckets) {
   1258  1.1  christos             auto ba = bucket_alloc(m_values.get_allocator());
   1259  1.1  christos             bucket_alloc_traits::deallocate(ba, m_buckets, bucket_count());
   1260  1.1  christos         }
   1261  1.1  christos     }
   1262  1.1  christos 
   1263  1.1  christos     auto operator=(table const& other) -> table& {
   1264  1.1  christos         if (&other != this) {
   1265  1.1  christos             deallocate_buckets(); // deallocate before m_values is set (might have another allocator)
   1266  1.1  christos             m_values = other.m_values;
   1267  1.1  christos             m_max_load_factor = other.m_max_load_factor;
   1268  1.1  christos             m_hash = other.m_hash;
   1269  1.1  christos             m_equal = other.m_equal;
   1270  1.1  christos             m_shifts = initial_shifts;
   1271  1.1  christos             copy_buckets(other);
   1272  1.1  christos         }
   1273  1.1  christos         return *this;
   1274  1.1  christos     }
   1275  1.1  christos 
   1276  1.1  christos     auto operator=(table&& other) noexcept(noexcept(std::is_nothrow_move_assignable_v<value_container_type> &&
   1277  1.1  christos                                                     std::is_nothrow_move_assignable_v<Hash> &&
   1278  1.1  christos                                                     std::is_nothrow_move_assignable_v<KeyEqual>)) -> table& {
   1279  1.1  christos         if (&other != this) {
   1280  1.1  christos             deallocate_buckets(); // deallocate before m_values is set (might have another allocator)
   1281  1.1  christos             m_values = std::move(other.m_values);
   1282  1.1  christos             other.m_values.clear();
   1283  1.1  christos 
   1284  1.1  christos             // we can only reuse m_buckets when both maps have the same allocator!
   1285  1.1  christos             if (get_allocator() == other.get_allocator()) {
   1286  1.1  christos                 m_buckets = std::exchange(other.m_buckets, nullptr);
   1287  1.1  christos                 m_num_buckets = std::exchange(other.m_num_buckets, 0);
   1288  1.1  christos                 m_max_bucket_capacity = std::exchange(other.m_max_bucket_capacity, 0);
   1289  1.1  christos                 m_shifts = std::exchange(other.m_shifts, initial_shifts);
   1290  1.1  christos                 m_max_load_factor = std::exchange(other.m_max_load_factor, default_max_load_factor);
   1291  1.1  christos                 m_hash = std::exchange(other.m_hash, {});
   1292  1.1  christos                 m_equal = std::exchange(other.m_equal, {});
   1293  1.1  christos                 other.allocate_buckets_from_shift();
   1294  1.1  christos                 other.clear_buckets();
   1295  1.1  christos             } else {
   1296  1.1  christos                 // set max_load_factor *before* copying the other's buckets, so we have the same
   1297  1.1  christos                 // behavior
   1298  1.1  christos                 m_max_load_factor = other.m_max_load_factor;
   1299  1.1  christos 
   1300  1.1  christos                 // copy_buckets sets m_buckets, m_num_buckets, m_max_bucket_capacity, m_shifts
   1301  1.1  christos                 copy_buckets(other);
   1302  1.1  christos                 // clear's the other's buckets so other is now already usable.
   1303  1.1  christos                 other.clear_buckets();
   1304  1.1  christos                 m_hash = other.m_hash;
   1305  1.1  christos                 m_equal = other.m_equal;
   1306  1.1  christos             }
   1307  1.1  christos             // map "other" is now already usable, it's empty.
   1308  1.1  christos         }
   1309  1.1  christos         return *this;
   1310  1.1  christos     }
   1311  1.1  christos 
   1312  1.1  christos     auto operator=(std::initializer_list<value_type> ilist) -> table& {
   1313  1.1  christos         clear();
   1314  1.1  christos         insert(ilist);
   1315  1.1  christos         return *this;
   1316  1.1  christos     }
   1317  1.1  christos 
   1318  1.1  christos     auto get_allocator() const noexcept -> allocator_type {
   1319  1.1  christos         return m_values.get_allocator();
   1320  1.1  christos     }
   1321  1.1  christos 
   1322  1.1  christos     // iterators //////////////////////////////////////////////////////////////
   1323  1.1  christos 
   1324  1.1  christos     auto begin() noexcept -> iterator {
   1325  1.1  christos         return m_values.begin();
   1326  1.1  christos     }
   1327  1.1  christos 
   1328  1.1  christos     auto begin() const noexcept -> const_iterator {
   1329  1.1  christos         return m_values.begin();
   1330  1.1  christos     }
   1331  1.1  christos 
   1332  1.1  christos     auto cbegin() const noexcept -> const_iterator {
   1333  1.1  christos         return m_values.cbegin();
   1334  1.1  christos     }
   1335  1.1  christos 
   1336  1.1  christos     auto end() noexcept -> iterator {
   1337  1.1  christos         return m_values.end();
   1338  1.1  christos     }
   1339  1.1  christos 
   1340  1.1  christos     auto cend() const noexcept -> const_iterator {
   1341  1.1  christos         return m_values.cend();
   1342  1.1  christos     }
   1343  1.1  christos 
   1344  1.1  christos     auto end() const noexcept -> const_iterator {
   1345  1.1  christos         return m_values.end();
   1346  1.1  christos     }
   1347  1.1  christos 
   1348  1.1  christos     // capacity ///////////////////////////////////////////////////////////////
   1349  1.1  christos 
   1350  1.1  christos     [[nodiscard]] auto empty() const noexcept -> bool {
   1351  1.1  christos         return m_values.empty();
   1352  1.1  christos     }
   1353  1.1  christos 
   1354  1.1  christos     [[nodiscard]] auto size() const noexcept -> size_t {
   1355  1.1  christos         return m_values.size();
   1356  1.1  christos     }
   1357  1.1  christos 
   1358  1.1  christos     [[nodiscard]] static constexpr auto max_size() noexcept -> size_t {
   1359  1.1  christos         if constexpr ((std::numeric_limits<value_idx_type>::max)() == (std::numeric_limits<size_t>::max)()) {
   1360  1.1  christos             return size_t{1} << (sizeof(value_idx_type) * 8 - 1);
   1361  1.1  christos         } else {
   1362  1.1  christos             return size_t{1} << (sizeof(value_idx_type) * 8);
   1363  1.1  christos         }
   1364  1.1  christos     }
   1365  1.1  christos 
   1366  1.1  christos     // modifiers //////////////////////////////////////////////////////////////
   1367  1.1  christos 
   1368  1.1  christos     void clear() {
   1369  1.1  christos         m_values.clear();
   1370  1.1  christos         clear_buckets();
   1371  1.1  christos     }
   1372  1.1  christos 
   1373  1.1  christos     auto insert(value_type const& value) -> std::pair<iterator, bool> {
   1374  1.1  christos         return emplace(value);
   1375  1.1  christos     }
   1376  1.1  christos 
   1377  1.1  christos     auto insert(value_type&& value) -> std::pair<iterator, bool> {
   1378  1.1  christos         return emplace(std::move(value));
   1379  1.1  christos     }
   1380  1.1  christos 
   1381  1.1  christos     template <class P, std::enable_if_t<std::is_constructible_v<value_type, P&&>, bool> = true>
   1382  1.1  christos     auto insert(P&& value) -> std::pair<iterator, bool> {
   1383  1.1  christos         return emplace(std::forward<P>(value));
   1384  1.1  christos     }
   1385  1.1  christos 
   1386  1.1  christos     auto insert(const_iterator /*hint*/, value_type const& value) -> iterator {
   1387  1.1  christos         return insert(value).first;
   1388  1.1  christos     }
   1389  1.1  christos 
   1390  1.1  christos     auto insert(const_iterator /*hint*/, value_type&& value) -> iterator {
   1391  1.1  christos         return insert(std::move(value)).first;
   1392  1.1  christos     }
   1393  1.1  christos 
   1394  1.1  christos     template <class P, std::enable_if_t<std::is_constructible_v<value_type, P&&>, bool> = true>
   1395  1.1  christos     auto insert(const_iterator /*hint*/, P&& value) -> iterator {
   1396  1.1  christos         return insert(std::forward<P>(value)).first;
   1397  1.1  christos     }
   1398  1.1  christos 
   1399  1.1  christos     template <class InputIt>
   1400  1.1  christos     void insert(InputIt first, InputIt last) {
   1401  1.1  christos         while (first != last) {
   1402  1.1  christos             insert(*first);
   1403  1.1  christos             ++first;
   1404  1.1  christos         }
   1405  1.1  christos     }
   1406  1.1  christos 
   1407  1.1  christos     void insert(std::initializer_list<value_type> ilist) {
   1408  1.1  christos         insert(ilist.begin(), ilist.end());
   1409  1.1  christos     }
   1410  1.1  christos 
   1411  1.1  christos     // nonstandard API: *this is emptied.
   1412  1.1  christos     // Also see "A Standard flat_map" https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0429r9.pdf
   1413  1.1  christos     auto extract() && -> value_container_type {
   1414  1.1  christos         return std::move(m_values);
   1415  1.1  christos     }
   1416  1.1  christos 
   1417  1.1  christos     // nonstandard API:
   1418  1.1  christos     // Discards the internally held container and replaces it with the one passed. Erases non-unique elements.
   1419  1.1  christos     auto replace(value_container_type&& container) {
   1420  1.1  christos         if (ANKERL_UNORDERED_DENSE_UNLIKELY(container.size() > max_size())) {
   1421  1.1  christos             on_error_too_many_elements();
   1422  1.1  christos         }
   1423  1.1  christos         auto shifts = calc_shifts_for_size(container.size());
   1424  1.1  christos         if (0 == m_num_buckets || shifts < m_shifts || container.get_allocator() != m_values.get_allocator()) {
   1425  1.1  christos             m_shifts = shifts;
   1426  1.1  christos             deallocate_buckets();
   1427  1.1  christos             allocate_buckets_from_shift();
   1428  1.1  christos         }
   1429  1.1  christos         clear_buckets();
   1430  1.1  christos 
   1431  1.1  christos         m_values = std::move(container);
   1432  1.1  christos 
   1433  1.1  christos         // can't use clear_and_fill_buckets_from_values() because container elements might not be unique
   1434  1.1  christos         auto value_idx = value_idx_type{};
   1435  1.1  christos 
   1436  1.1  christos         // loop until we reach the end of the container. duplicated entries will be replaced with back().
   1437  1.1  christos         while (value_idx != static_cast<value_idx_type>(m_values.size())) {
   1438  1.1  christos             auto const& key = get_key(m_values[value_idx]);
   1439  1.1  christos 
   1440  1.1  christos             auto hash = mixed_hash(key);
   1441  1.1  christos             auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
   1442  1.1  christos             auto bucket_idx = bucket_idx_from_hash(hash);
   1443  1.1  christos 
   1444  1.1  christos             bool key_found = false;
   1445  1.1  christos             while (true) {
   1446  1.1  christos                 auto const& bucket = at(m_buckets, bucket_idx);
   1447  1.1  christos                 if (dist_and_fingerprint > bucket.m_dist_and_fingerprint) {
   1448  1.1  christos                     break;
   1449  1.1  christos                 }
   1450  1.1  christos                 if (dist_and_fingerprint == bucket.m_dist_and_fingerprint &&
   1451  1.1  christos                     m_equal(key, get_key(m_values[bucket.m_value_idx]))) {
   1452  1.1  christos                     key_found = true;
   1453  1.1  christos                     break;
   1454  1.1  christos                 }
   1455  1.1  christos                 dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1456  1.1  christos                 bucket_idx = next(bucket_idx);
   1457  1.1  christos             }
   1458  1.1  christos 
   1459  1.1  christos             if (key_found) {
   1460  1.1  christos                 if (value_idx != static_cast<value_idx_type>(m_values.size() - 1)) {
   1461  1.1  christos                     m_values[value_idx] = std::move(m_values.back());
   1462  1.1  christos                 }
   1463  1.1  christos                 m_values.pop_back();
   1464  1.1  christos             } else {
   1465  1.1  christos                 place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
   1466  1.1  christos                 ++value_idx;
   1467  1.1  christos             }
   1468  1.1  christos         }
   1469  1.1  christos     }
   1470  1.1  christos 
   1471  1.1  christos     template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1472  1.1  christos     auto insert_or_assign(Key const& key, M&& mapped) -> std::pair<iterator, bool> {
   1473  1.1  christos         return do_insert_or_assign(key, std::forward<M>(mapped));
   1474  1.1  christos     }
   1475  1.1  christos 
   1476  1.1  christos     template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1477  1.1  christos     auto insert_or_assign(Key&& key, M&& mapped) -> std::pair<iterator, bool> {
   1478  1.1  christos         return do_insert_or_assign(std::move(key), std::forward<M>(mapped));
   1479  1.1  christos     }
   1480  1.1  christos 
   1481  1.1  christos     template <typename K,
   1482  1.1  christos               typename M,
   1483  1.1  christos               typename Q = T,
   1484  1.1  christos               typename H = Hash,
   1485  1.1  christos               typename KE = KeyEqual,
   1486  1.1  christos               std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
   1487  1.1  christos     auto insert_or_assign(K&& key, M&& mapped) -> std::pair<iterator, bool> {
   1488  1.1  christos         return do_insert_or_assign(std::forward<K>(key), std::forward<M>(mapped));
   1489  1.1  christos     }
   1490  1.1  christos 
   1491  1.1  christos     template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1492  1.1  christos     auto insert_or_assign(const_iterator /*hint*/, Key const& key, M&& mapped) -> iterator {
   1493  1.1  christos         return do_insert_or_assign(key, std::forward<M>(mapped)).first;
   1494  1.1  christos     }
   1495  1.1  christos 
   1496  1.1  christos     template <class M, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1497  1.1  christos     auto insert_or_assign(const_iterator /*hint*/, Key&& key, M&& mapped) -> iterator {
   1498  1.1  christos         return do_insert_or_assign(std::move(key), std::forward<M>(mapped)).first;
   1499  1.1  christos     }
   1500  1.1  christos 
   1501  1.1  christos     template <typename K,
   1502  1.1  christos               typename M,
   1503  1.1  christos               typename Q = T,
   1504  1.1  christos               typename H = Hash,
   1505  1.1  christos               typename KE = KeyEqual,
   1506  1.1  christos               std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
   1507  1.1  christos     auto insert_or_assign(const_iterator /*hint*/, K&& key, M&& mapped) -> iterator {
   1508  1.1  christos         return do_insert_or_assign(std::forward<K>(key), std::forward<M>(mapped)).first;
   1509  1.1  christos     }
   1510  1.1  christos 
   1511  1.1  christos     // Single arguments for unordered_set can be used without having to construct the value_type
   1512  1.1  christos     template <class K,
   1513  1.1  christos               typename Q = T,
   1514  1.1  christos               typename H = Hash,
   1515  1.1  christos               typename KE = KeyEqual,
   1516  1.1  christos               std::enable_if_t<!is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
   1517  1.1  christos     auto emplace(K&& key) -> std::pair<iterator, bool> {
   1518  1.1  christos         auto hash = mixed_hash(key);
   1519  1.1  christos         auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
   1520  1.1  christos         auto bucket_idx = bucket_idx_from_hash(hash);
   1521  1.1  christos 
   1522  1.1  christos         while (dist_and_fingerprint <= at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
   1523  1.1  christos             if (dist_and_fingerprint == at(m_buckets, bucket_idx).m_dist_and_fingerprint &&
   1524  1.1  christos                 m_equal(key, m_values[at(m_buckets, bucket_idx).m_value_idx])) {
   1525  1.1  christos                 // found it, return without ever actually creating anything
   1526  1.1  christos                 return {begin() + static_cast<difference_type>(at(m_buckets, bucket_idx).m_value_idx), false};
   1527  1.1  christos             }
   1528  1.1  christos             dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1529  1.1  christos             bucket_idx = next(bucket_idx);
   1530  1.1  christos         }
   1531  1.1  christos 
   1532  1.1  christos         // value is new, insert element first, so when exception happens we are in a valid state
   1533  1.1  christos         return do_place_element(dist_and_fingerprint, bucket_idx, std::forward<K>(key));
   1534  1.1  christos     }
   1535  1.1  christos 
   1536  1.1  christos     template <class... Args>
   1537  1.1  christos     auto emplace(Args&&... args) -> std::pair<iterator, bool> {
   1538  1.1  christos         // we have to instantiate the value_type to be able to access the key.
   1539  1.1  christos         // 1. emplace_back the object so it is constructed. 2. If the key is already there, pop it later in the loop.
   1540  1.1  christos         auto& key = get_key(m_values.emplace_back(std::forward<Args>(args)...));
   1541  1.1  christos         auto hash = mixed_hash(key);
   1542  1.1  christos         auto dist_and_fingerprint = dist_and_fingerprint_from_hash(hash);
   1543  1.1  christos         auto bucket_idx = bucket_idx_from_hash(hash);
   1544  1.1  christos 
   1545  1.1  christos         while (dist_and_fingerprint <= at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
   1546  1.1  christos             if (dist_and_fingerprint == at(m_buckets, bucket_idx).m_dist_and_fingerprint &&
   1547  1.1  christos                 m_equal(key, get_key(m_values[at(m_buckets, bucket_idx).m_value_idx]))) {
   1548  1.1  christos                 m_values.pop_back(); // value was already there, so get rid of it
   1549  1.1  christos                 return {begin() + static_cast<difference_type>(at(m_buckets, bucket_idx).m_value_idx), false};
   1550  1.1  christos             }
   1551  1.1  christos             dist_and_fingerprint = dist_inc(dist_and_fingerprint);
   1552  1.1  christos             bucket_idx = next(bucket_idx);
   1553  1.1  christos         }
   1554  1.1  christos 
   1555  1.1  christos         // value is new, place the bucket and shift up until we find an empty spot
   1556  1.1  christos         auto value_idx = static_cast<value_idx_type>(m_values.size() - 1);
   1557  1.1  christos         if (ANKERL_UNORDERED_DENSE_UNLIKELY(is_full())) {
   1558  1.1  christos             // increase_size just rehashes all the data we have in m_values
   1559  1.1  christos             increase_size();
   1560  1.1  christos         } else {
   1561  1.1  christos             // place element and shift up until we find an empty spot
   1562  1.1  christos             place_and_shift_up({dist_and_fingerprint, value_idx}, bucket_idx);
   1563  1.1  christos         }
   1564  1.1  christos         return {begin() + static_cast<difference_type>(value_idx), true};
   1565  1.1  christos     }
   1566  1.1  christos 
   1567  1.1  christos     template <class... Args>
   1568  1.1  christos     auto emplace_hint(const_iterator /*hint*/, Args&&... args) -> iterator {
   1569  1.1  christos         return emplace(std::forward<Args>(args)...).first;
   1570  1.1  christos     }
   1571  1.1  christos 
   1572  1.1  christos     template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1573  1.1  christos     auto try_emplace(Key const& key, Args&&... args) -> std::pair<iterator, bool> {
   1574  1.1  christos         return do_try_emplace(key, std::forward<Args>(args)...);
   1575  1.1  christos     }
   1576  1.1  christos 
   1577  1.1  christos     template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1578  1.1  christos     auto try_emplace(Key&& key, Args&&... args) -> std::pair<iterator, bool> {
   1579  1.1  christos         return do_try_emplace(std::move(key), std::forward<Args>(args)...);
   1580  1.1  christos     }
   1581  1.1  christos 
   1582  1.1  christos     template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1583  1.1  christos     auto try_emplace(const_iterator /*hint*/, Key const& key, Args&&... args) -> iterator {
   1584  1.1  christos         return do_try_emplace(key, std::forward<Args>(args)...).first;
   1585  1.1  christos     }
   1586  1.1  christos 
   1587  1.1  christos     template <class... Args, typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1588  1.1  christos     auto try_emplace(const_iterator /*hint*/, Key&& key, Args&&... args) -> iterator {
   1589  1.1  christos         return do_try_emplace(std::move(key), std::forward<Args>(args)...).first;
   1590  1.1  christos     }
   1591  1.1  christos 
   1592  1.1  christos     template <
   1593  1.1  christos         typename K,
   1594  1.1  christos         typename... Args,
   1595  1.1  christos         typename Q = T,
   1596  1.1  christos         typename H = Hash,
   1597  1.1  christos         typename KE = KeyEqual,
   1598  1.1  christos         std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE> && is_neither_convertible_v<K&&, iterator, const_iterator>,
   1599  1.1  christos                          bool> = true>
   1600  1.1  christos     auto try_emplace(K&& key, Args&&... args) -> std::pair<iterator, bool> {
   1601  1.1  christos         return do_try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
   1602  1.1  christos     }
   1603  1.1  christos 
   1604  1.1  christos     template <
   1605  1.1  christos         typename K,
   1606  1.1  christos         typename... Args,
   1607  1.1  christos         typename Q = T,
   1608  1.1  christos         typename H = Hash,
   1609  1.1  christos         typename KE = KeyEqual,
   1610  1.1  christos         std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE> && is_neither_convertible_v<K&&, iterator, const_iterator>,
   1611  1.1  christos                          bool> = true>
   1612  1.1  christos     auto try_emplace(const_iterator /*hint*/, K&& key, Args&&... args) -> iterator {
   1613  1.1  christos         return do_try_emplace(std::forward<K>(key), std::forward<Args>(args)...).first;
   1614  1.1  christos     }
   1615  1.1  christos 
   1616  1.1  christos     auto erase(iterator it) -> iterator {
   1617  1.1  christos         auto hash = mixed_hash(get_key(*it));
   1618  1.1  christos         auto bucket_idx = bucket_idx_from_hash(hash);
   1619  1.1  christos 
   1620  1.1  christos         auto const value_idx_to_remove = static_cast<value_idx_type>(it - cbegin());
   1621  1.1  christos         while (at(m_buckets, bucket_idx).m_value_idx != value_idx_to_remove) {
   1622  1.1  christos             bucket_idx = next(bucket_idx);
   1623  1.1  christos         }
   1624  1.1  christos 
   1625  1.1  christos         do_erase(bucket_idx, [](value_type&& /*unused*/) {
   1626  1.1  christos         });
   1627  1.1  christos         return begin() + static_cast<difference_type>(value_idx_to_remove);
   1628  1.1  christos     }
   1629  1.1  christos 
   1630  1.1  christos     auto extract(iterator it) -> value_type {
   1631  1.1  christos         auto hash = mixed_hash(get_key(*it));
   1632  1.1  christos         auto bucket_idx = bucket_idx_from_hash(hash);
   1633  1.1  christos 
   1634  1.1  christos         auto const value_idx_to_remove = static_cast<value_idx_type>(it - cbegin());
   1635  1.1  christos         while (at(m_buckets, bucket_idx).m_value_idx != value_idx_to_remove) {
   1636  1.1  christos             bucket_idx = next(bucket_idx);
   1637  1.1  christos         }
   1638  1.1  christos 
   1639  1.1  christos         auto tmp = std::optional<value_type>{};
   1640  1.1  christos         do_erase(bucket_idx, [&tmp](value_type&& val) {
   1641  1.1  christos             tmp = std::move(val);
   1642  1.1  christos         });
   1643  1.1  christos         return std::move(tmp).value();
   1644  1.1  christos     }
   1645  1.1  christos 
   1646  1.1  christos     template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1647  1.1  christos     auto erase(const_iterator it) -> iterator {
   1648  1.1  christos         return erase(begin() + (it - cbegin()));
   1649  1.1  christos     }
   1650  1.1  christos 
   1651  1.1  christos     template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1652  1.1  christos     auto extract(const_iterator it) -> value_type {
   1653  1.1  christos         return extract(begin() + (it - cbegin()));
   1654  1.1  christos     }
   1655  1.1  christos 
   1656  1.1  christos     auto erase(const_iterator first, const_iterator last) -> iterator {
   1657  1.1  christos         auto const idx_first = first - cbegin();
   1658  1.1  christos         auto const idx_last = last - cbegin();
   1659  1.1  christos         auto const first_to_last = std::distance(first, last);
   1660  1.1  christos         auto const last_to_end = std::distance(last, cend());
   1661  1.1  christos 
   1662  1.1  christos         // remove elements from left to right which moves elements from the end back
   1663  1.1  christos         auto const mid = idx_first + (std::min)(first_to_last, last_to_end);
   1664  1.1  christos         auto idx = idx_first;
   1665  1.1  christos         while (idx != mid) {
   1666  1.1  christos             erase(begin() + idx);
   1667  1.1  christos             ++idx;
   1668  1.1  christos         }
   1669  1.1  christos 
   1670  1.1  christos         // all elements from the right are moved, now remove the last element until all done
   1671  1.1  christos         idx = idx_last;
   1672  1.1  christos         while (idx != mid) {
   1673  1.1  christos             --idx;
   1674  1.1  christos             erase(begin() + idx);
   1675  1.1  christos         }
   1676  1.1  christos 
   1677  1.1  christos         return begin() + idx_first;
   1678  1.1  christos     }
   1679  1.1  christos 
   1680  1.1  christos     auto erase(Key const& key) -> size_t {
   1681  1.1  christos         return do_erase_key(key, [](value_type&& /*unused*/) {
   1682  1.1  christos         });
   1683  1.1  christos     }
   1684  1.1  christos 
   1685  1.1  christos     auto extract(Key const& key) -> std::optional<value_type> {
   1686  1.1  christos         auto tmp = std::optional<value_type>{};
   1687  1.1  christos         do_erase_key(key, [&tmp](value_type&& val) {
   1688  1.1  christos             tmp = std::move(val);
   1689  1.1  christos         });
   1690  1.1  christos         return tmp;
   1691  1.1  christos     }
   1692  1.1  christos 
   1693  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1694  1.1  christos     auto erase(K&& key) -> size_t {
   1695  1.1  christos         return do_erase_key(std::forward<K>(key), [](value_type&& /*unused*/) {
   1696  1.1  christos         });
   1697  1.1  christos     }
   1698  1.1  christos 
   1699  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1700  1.1  christos     auto extract(K&& key) -> std::optional<value_type> {
   1701  1.1  christos         auto tmp = std::optional<value_type>{};
   1702  1.1  christos         do_erase_key(std::forward<K>(key), [&tmp](value_type&& val) {
   1703  1.1  christos             tmp = std::move(val);
   1704  1.1  christos         });
   1705  1.1  christos         return tmp;
   1706  1.1  christos     }
   1707  1.1  christos 
   1708  1.1  christos     void swap(table& other) noexcept(noexcept(std::is_nothrow_swappable_v<value_container_type> &&
   1709  1.1  christos                                               std::is_nothrow_swappable_v<Hash> && std::is_nothrow_swappable_v<KeyEqual>)) {
   1710  1.1  christos         using std::swap;
   1711  1.1  christos         swap(other, *this);
   1712  1.1  christos     }
   1713  1.1  christos 
   1714  1.1  christos     // lookup /////////////////////////////////////////////////////////////////
   1715  1.1  christos 
   1716  1.1  christos     template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1717  1.1  christos     auto at(key_type const& key) -> Q& {
   1718  1.1  christos         return do_at(key);
   1719  1.1  christos     }
   1720  1.1  christos 
   1721  1.1  christos     template <typename K,
   1722  1.1  christos               typename Q = T,
   1723  1.1  christos               typename H = Hash,
   1724  1.1  christos               typename KE = KeyEqual,
   1725  1.1  christos               std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
   1726  1.1  christos     auto at(K const& key) -> Q& {
   1727  1.1  christos         return do_at(key);
   1728  1.1  christos     }
   1729  1.1  christos 
   1730  1.1  christos     template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1731  1.1  christos     auto at(key_type const& key) const -> Q const& {
   1732  1.1  christos         return do_at(key);
   1733  1.1  christos     }
   1734  1.1  christos 
   1735  1.1  christos     template <typename K,
   1736  1.1  christos               typename Q = T,
   1737  1.1  christos               typename H = Hash,
   1738  1.1  christos               typename KE = KeyEqual,
   1739  1.1  christos               std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
   1740  1.1  christos     auto at(K const& key) const -> Q const& {
   1741  1.1  christos         return do_at(key);
   1742  1.1  christos     }
   1743  1.1  christos 
   1744  1.1  christos     template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1745  1.1  christos     auto operator[](Key const& key) -> Q& {
   1746  1.1  christos         return try_emplace(key).first->second;
   1747  1.1  christos     }
   1748  1.1  christos 
   1749  1.1  christos     template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
   1750  1.1  christos     auto operator[](Key&& key) -> Q& {
   1751  1.1  christos         return try_emplace(std::move(key)).first->second;
   1752  1.1  christos     }
   1753  1.1  christos 
   1754  1.1  christos     template <typename K,
   1755  1.1  christos               typename Q = T,
   1756  1.1  christos               typename H = Hash,
   1757  1.1  christos               typename KE = KeyEqual,
   1758  1.1  christos               std::enable_if_t<is_map_v<Q> && is_transparent_v<H, KE>, bool> = true>
   1759  1.1  christos     auto operator[](K&& key) -> Q& {
   1760  1.1  christos         return try_emplace(std::forward<K>(key)).first->second;
   1761  1.1  christos     }
   1762  1.1  christos 
   1763  1.1  christos     auto count(Key const& key) const -> size_t {
   1764  1.1  christos         return find(key) == end() ? 0 : 1;
   1765  1.1  christos     }
   1766  1.1  christos 
   1767  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1768  1.1  christos     auto count(K const& key) const -> size_t {
   1769  1.1  christos         return find(key) == end() ? 0 : 1;
   1770  1.1  christos     }
   1771  1.1  christos 
   1772  1.1  christos     auto find(Key const& key) -> iterator {
   1773  1.1  christos         return do_find(key);
   1774  1.1  christos     }
   1775  1.1  christos 
   1776  1.1  christos     auto find(Key const& key) const -> const_iterator {
   1777  1.1  christos         return do_find(key);
   1778  1.1  christos     }
   1779  1.1  christos 
   1780  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1781  1.1  christos     auto find(K const& key) -> iterator {
   1782  1.1  christos         return do_find(key);
   1783  1.1  christos     }
   1784  1.1  christos 
   1785  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1786  1.1  christos     auto find(K const& key) const -> const_iterator {
   1787  1.1  christos         return do_find(key);
   1788  1.1  christos     }
   1789  1.1  christos 
   1790  1.1  christos     auto contains(Key const& key) const -> bool {
   1791  1.1  christos         return find(key) != end();
   1792  1.1  christos     }
   1793  1.1  christos 
   1794  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1795  1.1  christos     auto contains(K const& key) const -> bool {
   1796  1.1  christos         return find(key) != end();
   1797  1.1  christos     }
   1798  1.1  christos 
   1799  1.1  christos     auto equal_range(Key const& key) -> std::pair<iterator, iterator> {
   1800  1.1  christos         auto it = do_find(key);
   1801  1.1  christos         return {it, it == end() ? end() : it + 1};
   1802  1.1  christos     }
   1803  1.1  christos 
   1804  1.1  christos     auto equal_range(const Key& key) const -> std::pair<const_iterator, const_iterator> {
   1805  1.1  christos         auto it = do_find(key);
   1806  1.1  christos         return {it, it == end() ? end() : it + 1};
   1807  1.1  christos     }
   1808  1.1  christos 
   1809  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1810  1.1  christos     auto equal_range(K const& key) -> std::pair<iterator, iterator> {
   1811  1.1  christos         auto it = do_find(key);
   1812  1.1  christos         return {it, it == end() ? end() : it + 1};
   1813  1.1  christos     }
   1814  1.1  christos 
   1815  1.1  christos     template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
   1816  1.1  christos     auto equal_range(K const& key) const -> std::pair<const_iterator, const_iterator> {
   1817  1.1  christos         auto it = do_find(key);
   1818  1.1  christos         return {it, it == end() ? end() : it + 1};
   1819  1.1  christos     }
   1820  1.1  christos 
   1821  1.1  christos     // bucket interface ///////////////////////////////////////////////////////
   1822  1.1  christos 
   1823  1.1  christos     auto bucket_count() const noexcept -> size_t { // NOLINT(modernize-use-nodiscard)
   1824  1.1  christos         return m_num_buckets;
   1825  1.1  christos     }
   1826  1.1  christos 
   1827  1.1  christos     static constexpr auto max_bucket_count() noexcept -> size_t { // NOLINT(modernize-use-nodiscard)
   1828  1.1  christos         return max_size();
   1829  1.1  christos     }
   1830  1.1  christos 
   1831  1.1  christos     // hash policy ////////////////////////////////////////////////////////////
   1832  1.1  christos 
   1833  1.1  christos     [[nodiscard]] auto load_factor() const -> float {
   1834  1.1  christos         return bucket_count() ? static_cast<float>(size()) / static_cast<float>(bucket_count()) : 0.0F;
   1835  1.1  christos     }
   1836  1.1  christos 
   1837  1.1  christos     [[nodiscard]] auto max_load_factor() const -> float {
   1838  1.1  christos         return m_max_load_factor;
   1839  1.1  christos     }
   1840  1.1  christos 
   1841  1.1  christos     void max_load_factor(float ml) {
   1842  1.1  christos         m_max_load_factor = ml;
   1843  1.1  christos         if (m_num_buckets != max_bucket_count()) {
   1844  1.1  christos             m_max_bucket_capacity = static_cast<value_idx_type>(static_cast<float>(bucket_count()) * max_load_factor());
   1845  1.1  christos         }
   1846  1.1  christos     }
   1847  1.1  christos 
   1848  1.1  christos     void rehash(size_t count) {
   1849  1.1  christos         count = (std::min)(count, max_size());
   1850  1.1  christos         auto shifts = calc_shifts_for_size((std::max)(count, size()));
   1851  1.1  christos         if (shifts != m_shifts) {
   1852  1.1  christos             m_shifts = shifts;
   1853  1.1  christos             deallocate_buckets();
   1854  1.1  christos             m_values.shrink_to_fit();
   1855  1.1  christos             allocate_buckets_from_shift();
   1856  1.1  christos             clear_and_fill_buckets_from_values();
   1857  1.1  christos         }
   1858  1.1  christos     }
   1859  1.1  christos 
   1860  1.1  christos     void reserve(size_t capa) {
   1861  1.1  christos         capa = (std::min)(capa, max_size());
   1862  1.1  christos         if constexpr (has_reserve<value_container_type>) {
   1863  1.1  christos             // std::deque doesn't have reserve(). Make sure we only call when available
   1864  1.1  christos             m_values.reserve(capa);
   1865  1.1  christos         }
   1866  1.1  christos         auto shifts = calc_shifts_for_size((std::max)(capa, size()));
   1867  1.1  christos         if (0 == m_num_buckets || shifts < m_shifts) {
   1868  1.1  christos             m_shifts = shifts;
   1869  1.1  christos             deallocate_buckets();
   1870  1.1  christos             allocate_buckets_from_shift();
   1871  1.1  christos             clear_and_fill_buckets_from_values();
   1872  1.1  christos         }
   1873  1.1  christos     }
   1874  1.1  christos 
   1875  1.1  christos     // observers //////////////////////////////////////////////////////////////
   1876  1.1  christos 
   1877  1.1  christos     auto hash_function() const -> hasher {
   1878  1.1  christos         return m_hash;
   1879  1.1  christos     }
   1880  1.1  christos 
   1881  1.1  christos     auto key_eq() const -> key_equal {
   1882  1.1  christos         return m_equal;
   1883  1.1  christos     }
   1884  1.1  christos 
   1885  1.1  christos     // nonstandard API: expose the underlying values container
   1886  1.1  christos     [[nodiscard]] auto values() const noexcept -> value_container_type const& {
   1887  1.1  christos         return m_values;
   1888  1.1  christos     }
   1889  1.1  christos 
   1890  1.1  christos     // non-member functions ///////////////////////////////////////////////////
   1891  1.1  christos 
   1892  1.1  christos     friend auto operator==(table const& a, table const& b) -> bool {
   1893  1.1  christos         if (&a == &b) {
   1894  1.1  christos             return true;
   1895  1.1  christos         }
   1896  1.1  christos         if (a.size() != b.size()) {
   1897  1.1  christos             return false;
   1898  1.1  christos         }
   1899  1.1  christos         for (auto const& b_entry : b) {
   1900  1.1  christos             auto it = a.find(get_key(b_entry));
   1901  1.1  christos             if constexpr (is_map_v<T>) {
   1902  1.1  christos                 // map: check that key is here, then also check that value is the same
   1903  1.1  christos                 if (a.end() == it || !(b_entry.second == it->second)) {
   1904  1.1  christos                     return false;
   1905  1.1  christos                 }
   1906  1.1  christos             } else {
   1907  1.1  christos                 // set: only check that the key is here
   1908  1.1  christos                 if (a.end() == it) {
   1909  1.1  christos                     return false;
   1910  1.1  christos                 }
   1911  1.1  christos             }
   1912  1.1  christos         }
   1913  1.1  christos         return true;
   1914  1.1  christos     }
   1915  1.1  christos 
   1916  1.1  christos     friend auto operator!=(table const& a, table const& b) -> bool {
   1917  1.1  christos         return !(a == b);
   1918  1.1  christos     }
   1919  1.1  christos };
   1920  1.1  christos 
   1921  1.1  christos } // namespace detail
   1922  1.1  christos 
   1923  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1924  1.1  christos                                         class T,
   1925  1.1  christos                                         class Hash = hash<Key>,
   1926  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1927  1.1  christos                                         class AllocatorOrContainer = std::allocator<std::pair<Key, T>>,
   1928  1.1  christos                                         class Bucket = bucket_type::standard>
   1929  1.1  christos using map = detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, false>;
   1930  1.1  christos 
   1931  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1932  1.1  christos                                         class T,
   1933  1.1  christos                                         class Hash = hash<Key>,
   1934  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1935  1.1  christos                                         class AllocatorOrContainer = std::allocator<std::pair<Key, T>>,
   1936  1.1  christos                                         class Bucket = bucket_type::standard>
   1937  1.1  christos using segmented_map = detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, true>;
   1938  1.1  christos 
   1939  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1940  1.1  christos                                         class Hash = hash<Key>,
   1941  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1942  1.1  christos                                         class AllocatorOrContainer = std::allocator<Key>,
   1943  1.1  christos                                         class Bucket = bucket_type::standard>
   1944  1.1  christos using set = detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket, false>;
   1945  1.1  christos 
   1946  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1947  1.1  christos                                         class Hash = hash<Key>,
   1948  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1949  1.1  christos                                         class AllocatorOrContainer = std::allocator<Key>,
   1950  1.1  christos                                         class Bucket = bucket_type::standard>
   1951  1.1  christos using segmented_set = detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket, true>;
   1952  1.1  christos 
   1953  1.1  christos #    if defined(ANKERL_UNORDERED_DENSE_PMR)
   1954  1.1  christos 
   1955  1.1  christos namespace pmr {
   1956  1.1  christos 
   1957  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1958  1.1  christos                                         class T,
   1959  1.1  christos                                         class Hash = hash<Key>,
   1960  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1961  1.1  christos                                         class Bucket = bucket_type::standard>
   1962  1.1  christos using map =
   1963  1.1  christos     detail::table<Key, T, Hash, KeyEqual, ANKERL_UNORDERED_DENSE_PMR::polymorphic_allocator<std::pair<Key, T>>, Bucket, false>;
   1964  1.1  christos 
   1965  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1966  1.1  christos                                         class T,
   1967  1.1  christos                                         class Hash = hash<Key>,
   1968  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1969  1.1  christos                                         class Bucket = bucket_type::standard>
   1970  1.1  christos using segmented_map =
   1971  1.1  christos     detail::table<Key, T, Hash, KeyEqual, ANKERL_UNORDERED_DENSE_PMR::polymorphic_allocator<std::pair<Key, T>>, Bucket, true>;
   1972  1.1  christos 
   1973  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1974  1.1  christos                                         class Hash = hash<Key>,
   1975  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1976  1.1  christos                                         class Bucket = bucket_type::standard>
   1977  1.1  christos using set = detail::table<Key, void, Hash, KeyEqual, ANKERL_UNORDERED_DENSE_PMR::polymorphic_allocator<Key>, Bucket, false>;
   1978  1.1  christos 
   1979  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   1980  1.1  christos                                         class Hash = hash<Key>,
   1981  1.1  christos                                         class KeyEqual = std::equal_to<Key>,
   1982  1.1  christos                                         class Bucket = bucket_type::standard>
   1983  1.1  christos using segmented_set =
   1984  1.1  christos     detail::table<Key, void, Hash, KeyEqual, ANKERL_UNORDERED_DENSE_PMR::polymorphic_allocator<Key>, Bucket, true>;
   1985  1.1  christos 
   1986  1.1  christos } // namespace pmr
   1987  1.1  christos 
   1988  1.1  christos #    endif
   1989  1.1  christos 
   1990  1.1  christos // deduction guides ///////////////////////////////////////////////////////////
   1991  1.1  christos 
   1992  1.1  christos // deduction guides for alias templates are only possible since C++20
   1993  1.1  christos // see https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
   1994  1.1  christos 
   1995  1.1  christos } // namespace ANKERL_UNORDERED_DENSE_NAMESPACE
   1996  1.1  christos } // namespace ankerl::unordered_dense
   1997  1.1  christos 
   1998  1.1  christos // std extensions /////////////////////////////////////////////////////////////
   1999  1.1  christos 
   2000  1.1  christos namespace std { // NOLINT(cert-dcl58-cpp)
   2001  1.1  christos 
   2002  1.1  christos ANKERL_UNORDERED_DENSE_EXPORT template <class Key,
   2003  1.1  christos                                         class T,
   2004  1.1  christos                                         class Hash,
   2005  1.1  christos                                         class KeyEqual,
   2006  1.1  christos                                         class AllocatorOrContainer,
   2007  1.1  christos                                         class Bucket,
   2008  1.1  christos                                         class Pred,
   2009  1.1  christos                                         bool IsSegmented>
   2010  1.1  christos // NOLINTNEXTLINE(cert-dcl58-cpp)
   2011  1.1  christos auto erase_if(ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, IsSegmented>& map,
   2012  1.1  christos               Pred pred) -> size_t {
   2013  1.1  christos     using map_t = ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, IsSegmented>;
   2014  1.1  christos 
   2015  1.1  christos     // going back to front because erase() invalidates the end iterator
   2016  1.1  christos     auto const old_size = map.size();
   2017  1.1  christos     auto idx = old_size;
   2018  1.1  christos     while (idx) {
   2019  1.1  christos         --idx;
   2020  1.1  christos         auto it = map.begin() + static_cast<typename map_t::difference_type>(idx);
   2021  1.1  christos         if (pred(*it)) {
   2022  1.1  christos             map.erase(it);
   2023  1.1  christos         }
   2024  1.1  christos     }
   2025  1.1  christos 
   2026  1.1  christos     return old_size - map.size();
   2027  1.1  christos }
   2028  1.1  christos 
   2029  1.1  christos } // namespace std
   2030  1.1  christos 
   2031  1.1  christos #endif
   2032  1.1  christos #endif
   2033