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