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