Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
robin_hood.h
1// ______ _____ ______ _________
2// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ /
3// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ /
4// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ /
5// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/
6// _/_____/
7//
8// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20
9// https://github.com/martinus/robin-hood-hashing
10//
11// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
12// SPDX-License-Identifier: MIT
13// Copyright (c) 2018-2021 Martin Ankerl <http://martin.ankerl.com>
14//
15// Permission is hereby granted, free of charge, to any person obtaining a copy
16// of this software and associated documentation files (the "Software";, to deal
17// in the Software without restriction, including without limitation the rights
18// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19// copies of the Software, and to permit persons to whom the Software is
20// furnished to do so, subject to the following conditions:
21//
22// The above copyright notice and this permission notice shall be included in all
23// copies or substantial portions of the Software.
24//
25// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31// SOFTWARE.
32
33#ifndef ROBIN_HOOD_H_INCLUDED
34#define ROBIN_HOOD_H_INCLUDED
35
36// see https://semver.org/
37#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes
38#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner
39#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes
40
41#include "gaia/config/config_core.h"
42
43#include <cstdlib>
44#include <cstring>
45#include <initializer_list>
46#include <new>
47#include <tuple>
48#include <type_traits>
49#include <utility>
50
51#include "gaia/core/hashing_policy.h"
52#include "gaia/core/iterator.h"
53#include "gaia/core/utility.h"
54#include "gaia/mem/mem_alloc.h"
55#include "gaia/mem/mem_utils.h"
56#include "gaia/ser/ser_ct.h"
57#include "gaia/util/logging.h"
58
59// #define ROBIN_HOOD_STD_SMARTPOINTERS
60#if defined(ROBIN_HOOD_STD_SMARTPOINTERS)
61 #include <memory>
62#endif
63
64// #define ROBIN_HOOD_LOG_ENABLED
65#ifdef ROBIN_HOOD_LOG_ENABLED
66 #define ROBIN_HOOD_LOG(x, ...) GAIA_LOG_D("L:%s@%d: " x, __FUNCTION__, __LINE__, ##__VA_ARGS__)
67#else
68 #define ROBIN_HOOD_LOG(x, ...)
69#endif
70
71// #define ROBIN_HOOD_TRACE_ENABLED
72#ifdef ROBIN_HOOD_TRACE_ENABLED
73 #define ROBIN_HOOD_TRACE(x, ...) GAIA_LOG_D("T:%s@%d: " x, __FUNCTION__, __LINE__, ##__VA_ARGS__)
74#else
75 #define ROBIN_HOOD_TRACE(x, ...)
76#endif
77
78// all non-argument macros should use this facility. See
79// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/
80#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x()
81
82// mark unused members with this macro
83#define ROBIN_HOOD_UNUSED(identifier)
84
85// bitness
86#if SIZE_MAX == UINT32_MAX
87 #define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32
88#elif SIZE_MAX == UINT64_MAX
89 #define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64
90#else
91 #error Unsupported bitness
92#endif
93
94// exceptions
95#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
96 #define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0
97 #define ROBIN_HOOD_STD_OUT_OF_RANGE void
98#else
99 #include <stdexcept>
100 #define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1
101 #define ROBIN_HOOD_STD_OUT_OF_RANGE std::out_of_range
102#endif
103
104// count leading/trailing bits
105#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS)
106 #if ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() == 32
107 #define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) GAIA_CLZ(x)
108 #define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) GAIA_CTZ(x)
109 #else
110 #define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) GAIA_CLZ64(x)
111 #define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) GAIA_CTZ64(x)
112 #endif
113#endif
114
115// fallthrough
116#ifndef __has_cpp_attribute // For backwards compatibility
117 #define __has_cpp_attribute(x) 0
118#endif
119#if __has_cpp_attribute(fallthrough)
120 #define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[fallthrough]]
121#else
122 #define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH()
123#endif
124
125// detect if native wchar_t type is availiable in MSVC
126#ifdef _MSC_VER
127 #ifdef _NATIVE_WCHAR_T_DEFINED
128 #define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1
129 #else
130 #define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0
131 #endif
132#else
133 #define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1
134#endif
135
136// detect if MSVC supports the pair(std::piecewise_construct_t,...) constructor being constexpr
137#ifdef _MSC_VER
138 #if _MSC_VER <= 1900
139 #define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1
140 #else
141 #define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0
142 #endif
143#else
144 #define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0
145#endif
146
147// workaround missing "is_trivially_copyable" in g++ < 5.0
148// See https://stackoverflow.com/a/31798726/48181
149#if GAIA_COMPILER_GCC && __GNUC__ < 5
150 #define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
151#else
152 #define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
153#endif
154
155namespace robin_hood {
156
157 namespace detail {
158
159// make sure we static_cast to the correct type for hash_int
160#if ROBIN_HOOD(BITNESS) == 64
161 using SizeT = uint64_t;
162#else
163 using SizeT = uint32_t;
164#endif
165
166 template <typename T>
167 T rotr(T x, unsigned k) {
168 return (x >> k) | (x << (8U * sizeof(T) - k));
169 }
170
171 // This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to
172 // 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with
173 // care!
174 template <typename T>
175 inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept {
176 return reinterpret_cast<T>(ptr);
177 }
178
179 template <typename T>
180 inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept {
181 return reinterpret_cast<T>(ptr);
182 }
183
184 template <typename, typename = void>
185 struct has_hash: public std::false_type {};
186
187 template <typename T>
188 struct has_hash<T, std::void_t<decltype(T::hash)>>: public std::true_type {};
189
190 // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
191 // inlinings more difficult. Throws are also generally the slow path.
192 template <typename E, typename... Args>
193 [[noreturn]] GAIA_NOINLINE
194#if ROBIN_HOOD(HAS_EXCEPTIONS)
195 void doThrow(Args&&... args) {
196 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
197 throw E(GAIA_FWD(args)...);
198 }
199#else
200 void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
201 abort();
202 }
203#endif
204
205 template <typename E, typename T, typename... Args>
206 T* assertNotNull(T* t, Args&&... args) {
207 if GAIA_UNLIKELY (nullptr == t) {
208 doThrow<E>(GAIA_FWD(args)...);
209 }
210 return t;
211 }
212
213 template <typename T>
214 inline T unaligned_load(void const* ptr) noexcept {
215 // using memcpy so we don't get into unaligned load problems.
216 // compiler should optimize this very well anyways.
217 T t;
218 memcpy(&t, ptr, sizeof(T));
219 return t;
220 }
221
222 // Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor,
223 // and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a
224 // pointer.
225 template <typename T, size_t MinNumAllocs = 4, size_t MaxNumAllocs = 256>
227 public:
228 BulkPoolAllocator() noexcept = default;
229
230 // does not copy anything, just creates a new allocator.
231 BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept:
232 mHead(nullptr), mListForFree(nullptr) {}
233
234 BulkPoolAllocator(BulkPoolAllocator&& o) noexcept: mHead(o.mHead), mListForFree(o.mListForFree) {
235 o.mListForFree = nullptr;
236 o.mHead = nullptr;
237 }
238
239 BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept {
240 reset();
241 mHead = o.mHead;
242 mListForFree = o.mListForFree;
243 o.mListForFree = nullptr;
244 o.mHead = nullptr;
245 return *this;
246 }
247
249 // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
250 operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept {
251 // does not do anything
252 return *this;
253 }
254
255 ~BulkPoolAllocator() noexcept {
256 reset();
257 }
258
259 // Deallocates all allocated memory.
260 void reset() noexcept {
261 while (mListForFree) {
262 T* tmp = *mListForFree;
263 ROBIN_HOOD_LOG("gaia::mem::mem_free");
264 gaia::mem::mem_free(mListForFree);
265 mListForFree = reinterpret_cast_no_cast_align_warning<T**>(tmp);
266 }
267 mHead = nullptr;
268 }
269
270 // allocates, but does NOT initialize. Use in-place new constructor, e.g.
271 // T* obj = pool.allocate();
272 // ::new (static_cast<void*>(obj)) T();
273 T* allocate() {
274 T* tmp = mHead;
275 if (tmp == nullptr) {
276 tmp = performAllocation();
277 }
278
279 mHead = *reinterpret_cast_no_cast_align_warning<T**>(tmp);
280 return tmp;
281 }
282
283 // does not actually deallocate but puts it in store.
284 // make sure you have already called the destructor! e.g. with
285 // obj->~T();
286 // pool.deallocate(obj);
287 void deallocate(T* obj) noexcept {
288 *reinterpret_cast_no_cast_align_warning<T**>(obj) = mHead;
289 mHead = obj;
290 }
291
292 // Adds an already allocated block of memory to the allocator. This allocator is from now on
293 // responsible for freeing the data (with mem_free()). If the provided data is not large enough to
294 // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor.
295 void addOrFree(void* ptr, const size_t numBytes) noexcept {
296 // calculate number of available elements in ptr
297 if (numBytes < ALIGNMENT + ALIGNED_SIZE) {
298 // not enough data for at least one element. Free and return.
299 ROBIN_HOOD_LOG("gaia::mem::mem_free");
300 gaia::mem::mem_free(ptr);
301 } else {
302 ROBIN_HOOD_LOG("add to buffer");
303 add(ptr, numBytes);
304 }
305 }
306
307 void swap(BulkPoolAllocator<T, MinNumAllocs, MaxNumAllocs>& other) noexcept {
308 using gaia::core::swap;
309 swap(mHead, other.mHead);
310 swap(mListForFree, other.mListForFree);
311 }
312
313 private:
314 // iterates the list of allocated memory to calculate how many to alloc next.
315 // Recalculating this each time saves us a size_t member.
316 // This ignores the fact that memory blocks might have been added manually with addOrFree. In
317 // practice, this should not matter much.
318 GAIA_NODISCARD size_t calcNumElementsToAlloc() const noexcept {
319 auto tmp = mListForFree;
320 size_t numAllocs = MinNumAllocs;
321
322 while (numAllocs * 2 <= MaxNumAllocs && tmp) {
323 auto x = reinterpret_cast<T***>(tmp);
324 tmp = *x;
325 numAllocs *= 2;
326 }
327
328 return numAllocs;
329 }
330
331 // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree().
332 void add(void* ptr, const size_t numBytes) noexcept {
333 const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE;
334
335 auto data = reinterpret_cast<T**>(ptr);
336
337 // link free list
338 auto x = reinterpret_cast<T***>(data);
339 *x = mListForFree;
340 mListForFree = data;
341
342 // create linked list for newly allocated data
343 auto* const headT = reinterpret_cast_no_cast_align_warning<T*>(reinterpret_cast<char*>(ptr) + ALIGNMENT);
344
345 auto* const head = reinterpret_cast<char*>(headT);
346
347 // Visual Studio compiler automatically unrolls this loop, which is pretty cool
348 for (size_t i = 0; i < numElements; ++i) {
349 *reinterpret_cast_no_cast_align_warning<char**>(head + i * ALIGNED_SIZE) = head + (i + 1) * ALIGNED_SIZE;
350 }
351
352 // last one points to 0
353 *reinterpret_cast_no_cast_align_warning<T**>(head + (numElements - 1) * ALIGNED_SIZE) = mHead;
354 mHead = headT;
355 }
356
357 // Called when no memory is available (mHead == 0).
358 // Don't inline this slow path.
359 GAIA_NOINLINE T* performAllocation() {
360 size_t const numElementsToAlloc = calcNumElementsToAlloc();
361
362 // alloc new memory: [prev |T, T, ... T]
363 size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc;
364 ROBIN_HOOD_LOG(
365 "gaia::mem::mem_alloc %zu = %zu + %zu * %zu", bytes, ALIGNMENT, ALIGNED_SIZE, numElementsToAlloc);
366 add(assertNotNull<std::bad_alloc>(gaia::mem::mem_alloc(bytes)), bytes);
367 return mHead;
368 }
369
370 // enforce byte alignment of the T's
371 static constexpr size_t ALIGNMENT =
372 gaia::core::get_max(std::alignment_of<T>::value, std::alignment_of<T*>::value);
373
374 static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT;
375
376 static_assert(MinNumAllocs >= 1, "MinNumAllocs");
377 static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs");
378 static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE");
379 static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod");
380 static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT");
381
382 T* mHead{nullptr};
383 T** mListForFree{nullptr};
384 };
385
386 template <typename T, size_t MinSize, size_t MaxSize, bool IsFlat>
388
389 // dummy allocator that does nothing
390 template <typename T, size_t MinSize, size_t MaxSize>
391 struct NodeAllocator<T, MinSize, MaxSize, true> {
392
393 // we are not using the data, so just free it.
394 void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept {
395 ROBIN_HOOD_LOG("gaia::mem::mem_free");
396 gaia::mem::mem_free(ptr);
397 }
398 };
399
400 template <typename T, size_t MinSize, size_t MaxSize>
401 struct NodeAllocator<T, MinSize, MaxSize, false>: public BulkPoolAllocator<T, MinSize, MaxSize> {};
402
403 namespace swappable {
404 template <typename T>
405 struct nothrow {
406 static const bool value = std::is_nothrow_swappable<T>::value;
407 };
408 } // namespace swappable
409
410 } // namespace detail
411
413
414 // A custom pair implementation is used in the map because std::pair is not is_trivially_copyable,
415 // which means it would not be allowed to be used in memcpy. This struct is copyable, which is
416 // also tested.
417 template <typename T1, typename T2>
418 struct pair {
419 using first_type = T1;
420 using second_type = T2;
421
422 template <
423 typename U1 = T1, typename U2 = T2,
424 typename = typename std::enable_if<
425 std::is_default_constructible<U1>::value && std::is_default_constructible<U2>::value>::type>
426 constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())): first(), second() {}
427
428 // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
429 explicit constexpr pair(std::pair<T1, T2> const& o) noexcept(
430 noexcept(T1(std::declval<T1 const&>())) && noexcept(T2(std::declval<T2 const&>()))):
431 first(o.first), second(o.second) {}
432
433 // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
434 explicit constexpr pair(std::pair<T1, T2>&& o) noexcept(
435 noexcept(T1(GAIA_MOV(std::declval<T1&&>()))) && noexcept(T2(GAIA_MOV(std::declval<T2&&>())))):
436 first(GAIA_MOV(o.first)), second(GAIA_MOV(o.second)) {}
437
438 constexpr pair(T1&& a, T2&& b) noexcept(
439 noexcept(T1(GAIA_MOV(std::declval<T1&&>()))) && noexcept(T2(GAIA_MOV(std::declval<T2&&>())))):
440 first(GAIA_MOV(a)), second(GAIA_MOV(b)) {}
441
442 template <typename U1, typename U2>
443 constexpr pair(U1&& a, U2&& b) noexcept(
444 noexcept(T1(GAIA_FWD(std::declval<U1&&>()))) && noexcept(T2(GAIA_FWD(std::declval<U2&&>())))):
445 first(GAIA_FWD(a)), second(GAIA_FWD(b)) {}
446
447 template <typename... U1, typename... U2>
448 // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members"
449 // if this constructor is constexpr
450#if !ROBIN_HOOD(BROKEN_CONSTEXPR)
451 constexpr
452#endif
453 pair(std::piecewise_construct_t /*unused*/, std::tuple<U1...> a, std::tuple<U2...> b) noexcept(noexcept(pair(
454 std::declval<std::tuple<U1...>&>(), std::declval<std::tuple<U2...>&>(), std::index_sequence_for<U1...>(),
455 std::index_sequence_for<U2...>()))):
456 pair(a, b, std::index_sequence_for<U1...>(), std::index_sequence_for<U2...>()) {
457 }
458
459 // constructor called from the std::piecewise_construct_t ctor
460 template <typename... U1, size_t... I1, typename... U2, size_t... I2>
461 pair(std::tuple<U1...>& a, std::tuple<U2...>& b, std::index_sequence<I1...> /*unused*/, std::index_sequence<I2...> /*unused*/) noexcept(
462 noexcept(T1(GAIA_FWD(std::get<I1>(std::declval<std::tuple<U1...>&>()))...)) &&
463 noexcept(T2(GAIA_FWD(std::get<I2>(std::declval<std::tuple<U2...>&>()))...))):
464 first(GAIA_FWD(std::get<I1>(a))...), second(GAIA_FWD(std::get<I2>(b))...) {
465 // make visual studio compiler happy about warning about unused a & b.
466 // Visual studio's pair implementation disables warning 4100.
467 (void)a;
468 (void)b;
469 }
470
471 void
473 using gaia::core::swap;
474 swap(first, o.first);
475 swap(second, o.second);
476 }
477
478 T1 first; // NOLINT(misc-non-private-member-variables-in-classes)
479 T2 second; // NOLINT(misc-non-private-member-variables-in-classes)
480 };
481
482 template <typename A, typename B>
483 inline void
484 swap(pair<A, B>& a, pair<A, B>& b) noexcept(noexcept(std::declval<pair<A, B>&>().swap(std::declval<pair<A, B>&>()))) {
485 a.swap(b);
486 }
487
488 template <typename A, typename B>
489 inline constexpr bool operator==(pair<A, B> const& x, pair<A, B> const& y) {
490 return (x.first == y.first) && (x.second == y.second);
491 }
492 template <typename A, typename B>
493 inline constexpr bool operator!=(pair<A, B> const& x, pair<A, B> const& y) {
494 return !(x == y);
495 }
496 template <typename A, typename B>
497 inline constexpr bool operator<(pair<A, B> const& x, pair<A, B> const& y) noexcept(
498 noexcept(std::declval<A const&>() < std::declval<A const&>()) &&
499 noexcept(std::declval<B const&>() < std::declval<B const&>())) {
500 return x.first < y.first || (!(y.first < x.first) && x.second < y.second);
501 }
502 template <typename A, typename B>
503 inline constexpr bool operator>(pair<A, B> const& x, pair<A, B> const& y) {
504 return y < x;
505 }
506 template <typename A, typename B>
507 inline constexpr bool operator<=(pair<A, B> const& x, pair<A, B> const& y) {
508 return !(x > y);
509 }
510 template <typename A, typename B>
511 inline constexpr bool operator>=(pair<A, B> const& x, pair<A, B> const& y) {
512 return !(x < y);
513 }
514
515 inline size_t hash_bytes(void const* ptr, size_t len) noexcept {
516 static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
517 static constexpr uint64_t seed = UINT64_C(0xe17a1465);
518 static constexpr unsigned int r = 47;
519
520 auto const* const data64 = static_cast<uint64_t const*>(ptr);
521 uint64_t h = seed ^ (len * m);
522
523 size_t const n_blocks = len / 8;
524 for (size_t i = 0; i < n_blocks; ++i) {
525 auto k = detail::unaligned_load<uint64_t>(data64 + i);
526
527 k *= m;
528 k ^= k >> r;
529 k *= m;
530
531 h ^= k;
532 h *= m;
533 }
534
535 auto const* const data8 = reinterpret_cast<uint8_t const*>(data64 + n_blocks);
536 switch (len & 7U) {
537 case 7:
538 h ^= static_cast<uint64_t>(data8[6]) << 48U;
539 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
540 case 6:
541 h ^= static_cast<uint64_t>(data8[5]) << 40U;
542 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
543 case 5:
544 h ^= static_cast<uint64_t>(data8[4]) << 32U;
545 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
546 case 4:
547 h ^= static_cast<uint64_t>(data8[3]) << 24U;
548 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
549 case 3:
550 h ^= static_cast<uint64_t>(data8[2]) << 16U;
551 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
552 case 2:
553 h ^= static_cast<uint64_t>(data8[1]) << 8U;
554 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
555 case 1:
556 h ^= static_cast<uint64_t>(data8[0]);
557 h *= m;
558 ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
559 default:
560 break;
561 }
562
563 h ^= h >> r;
564
565 // not doing the final step here, because this will be done by keyToIdx anyways
566 // h *= m;
567 // h ^= h >> r;
568 return static_cast<size_t>(h);
569 }
570
571 inline size_t hash_int(uint64_t x) noexcept {
572 // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested,
573 // and doesn't need any special 128bit operations.
574 x ^= x >> 33U;
575 x *= UINT64_C(0xff51afd7ed558ccd);
576 x ^= x >> 33U;
577
578 // not doing the final step here, because this will be done by keyToIdx anyways
579 // x *= UINT64_C(0xc4ceb9fe1a85ec53);
580 // x ^= x >> 33U;
581 return static_cast<size_t>(x);
582 }
583
584 template <typename T, typename Enable = void>
585 struct hash {
586 size_t operator()(T const& obj) const noexcept {
587 return hash_bytes(&obj, sizeof(T));
588 }
589 };
590
591 template <typename T>
592 struct hash<T*> {
593 size_t operator()(T* ptr) const noexcept {
594 return hash_int(reinterpret_cast<detail::SizeT>(ptr));
595 }
596 };
597
598#ifdef ROBIN_HOOD_STD_SMARTPOINTERS
599 template <typename T>
600 struct hash<std::unique_ptr<T>> {
601 size_t operator()(std::unique_ptr<T> const& ptr) const noexcept {
602 return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
603 }
604 };
605
606 template <typename T>
607 struct hash<std::shared_ptr<T>> {
608 size_t operator()(std::shared_ptr<T> const& ptr) const noexcept {
609 return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
610 }
611 };
612#endif
613
614 template <typename Enum>
615 struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
616 size_t operator()(Enum e) const noexcept {
617 using Underlying = typename std::underlying_type<Enum>::type;
618 return hash<Underlying>{}(static_cast<Underlying>(e));
619 }
620 };
621
622 template <typename T>
623 struct hash<T, typename std::enable_if<gaia::core::is_direct_hash_key_v<T>>::type> {
624 size_t operator()(const T& obj) const noexcept {
625 if constexpr (detail::has_hash<T>::value)
626 return obj.hash;
627 else
628 return obj.hash();
629 }
630 };
631
632#define ROBIN_HOOD_HASH_INT(T) \
633 template <> \
634 struct hash<T> { \
635 size_t operator()(T const& obj) const noexcept { \
636 return hash_int(static_cast<uint64_t>(obj)); \
637 } \
638 }
639
640#if defined(__GNUC__) && !defined(__clang__)
641 #pragma GCC diagnostic push
642 #pragma GCC diagnostic ignored "-Wuseless-cast"
643#endif
644 // see https://en.cppreference.com/w/cpp/utility/hash
645 ROBIN_HOOD_HASH_INT(bool);
646 ROBIN_HOOD_HASH_INT(char);
647 ROBIN_HOOD_HASH_INT(signed char);
648 ROBIN_HOOD_HASH_INT(unsigned char);
649 ROBIN_HOOD_HASH_INT(char16_t);
650 ROBIN_HOOD_HASH_INT(char32_t);
651#if ROBIN_HOOD(HAS_NATIVE_WCHART)
652 ROBIN_HOOD_HASH_INT(wchar_t);
653#endif
654 ROBIN_HOOD_HASH_INT(short);
655 ROBIN_HOOD_HASH_INT(unsigned short);
656 ROBIN_HOOD_HASH_INT(int);
657 ROBIN_HOOD_HASH_INT(unsigned int);
658 ROBIN_HOOD_HASH_INT(long);
659 ROBIN_HOOD_HASH_INT(long long);
660 ROBIN_HOOD_HASH_INT(unsigned long);
661 ROBIN_HOOD_HASH_INT(unsigned long long);
662#if defined(__GNUC__) && !defined(__clang__)
663 #pragma GCC diagnostic pop
664#endif
665 namespace detail {
666 template <typename, typename = void>
667 struct has_is_transparent: public std::false_type {};
668
669 template <typename T>
670 struct has_is_transparent<T, std::void_t<decltype(T::is_transparent)>>: public std::true_type {};
671
672 // using wrapper classes for hash and key_equal prevents the diamond problem when the same type
673 // is used. see https://stackoverflow.com/a/28771920/48181
674 template <typename T>
675 struct WrapHash: public T {
676 WrapHash() = default;
677 explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval<T const&>()))): T(o) {}
678 };
679
680 template <typename T>
681 struct WrapKeyEqual: public T {
682 WrapKeyEqual() = default;
683 explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval<T const&>()))): T(o) {}
684 };
685
686 // A highly optimized hashmap implementation, using the Robin Hood algorithm.
687 //
688 // In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but
689 // be about 2x faster in most cases and require much less allocations.
690 //
691 // This implementation uses the following memory layout:
692 //
693 // [Node, Node, ... Node | info, info, ... infoSentinel ]
694 //
695 // * Node: either a DataNode that directly has the std::pair<key, val> as member,
696 // or a DataNode with a pointer to std::pair<key,val>. Which DataNode representation to use
697 // depends on how fast the swap() operation is. Heuristically, this is automatically choosen
698 // based on sizeof(). there are always 2^n Nodes.
699 //
700 // * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes.
701 // Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the
702 // corresponding node contains data. Set to 2 means the corresponding Node is filled, but it
703 // actually belongs to the previous position and was pushed out because that place is already
704 // taken.
705 //
706 // * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the
707 // need for a idx variable.
708 //
709 // According to STL, order of templates has effect on throughput. That's why I've moved the
710 // boolean to the front.
711 // https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/
712 template <bool IsFlat, size_t MaxLoadFactor100, typename Key, typename T, typename Hash, typename KeyEqual>
713 class Table:
714 public WrapHash<Hash>,
715 public WrapKeyEqual<KeyEqual>,
717 typename std::conditional<
718 std::is_void<T>::value, Key,
719 robin_hood::pair<typename std::conditional<IsFlat, Key, Key const>::type, T>>::type,
720 4, 16384, IsFlat> {
721 public:
722 static constexpr bool is_flat = IsFlat;
723 static constexpr bool is_map = !std::is_void<T>::value;
724 static constexpr bool is_set = !is_map;
725 static constexpr bool is_transparent = has_is_transparent<Hash>::value && has_is_transparent<KeyEqual>::value;
726
727 using key_type = Key;
728 using mapped_type = T;
729 using value_type = typename std::conditional<
731 using size_type = size_t;
732 using hasher = Hash;
733 using key_equal = KeyEqual;
735
736 private:
737 static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, "MaxLoadFactor100 needs to be >10 && < 100");
738
739 using WHash = WrapHash<Hash>;
741
742 // configuration defaults
743
744 // make sure we have 8 elements, needed to quickly rehash mInfo
745 static constexpr size_t InitialNumElements = sizeof(uint64_t);
746 static constexpr uint32_t InitialInfoNumBits = 5;
747 static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits;
748 static constexpr size_t InfoMask = InitialInfoInc - 1U;
749 static constexpr uint8_t InitialInfoHashShift = 64U - InitialInfoNumBits;
751
752 // type needs to be wider than uint8_t.
753 using InfoType = uint32_t;
754
755 // DataNode ////////////////////////////////////////////////////////
756
757 // Primary template for the data node. We have special implementations for small and big
758 // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these
759 // on the heap so swap merely swaps a pointer.
760 template <typename M, bool>
761 class DataNode {};
762
763 // Small: just allocate on the stack.
764 template <typename M>
765 class DataNode<M, true> final {
766 public:
767 template <typename... Args>
768 explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept(
769 noexcept(value_type(GAIA_FWD(args)...))): mData(GAIA_FWD(args)...) {}
770
771 DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, true>&& n) noexcept(
772 std::is_nothrow_move_constructible<value_type>::value): mData(GAIA_MOV(n.mData)) {}
773
774 // doesn't do anything
775 void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {}
776 void destroyDoNotDeallocate() noexcept {}
777
778 value_type const* operator->() const noexcept {
779 return &mData;
780 }
781 value_type* operator->() noexcept {
782 return &mData;
783 }
784
785 const value_type& operator*() const noexcept {
786 return mData;
787 }
788
789 value_type& operator*() noexcept {
790 return mData;
791 }
792
793 template <typename VT = value_type>
794 GAIA_NODISCARD typename std::enable_if<is_map, typename VT::first_type&>::type getFirst() noexcept {
795 return mData.first;
796 }
797 template <typename VT = value_type>
798 GAIA_NODISCARD typename std::enable_if<is_set, VT&>::type getFirst() noexcept {
799 return mData;
800 }
801
802 template <typename VT = value_type>
803 GAIA_NODISCARD typename std::enable_if<is_map, typename VT::first_type const&>::type getFirst() const noexcept {
804 return mData.first;
805 }
806 template <typename VT = value_type>
807 GAIA_NODISCARD typename std::enable_if<is_set, VT const&>::type getFirst() const noexcept {
808 return mData;
809 }
810
811 template <typename MT = mapped_type>
812 GAIA_NODISCARD typename std::enable_if<is_map, MT&>::type getSecond() noexcept {
813 return mData.second;
814 }
815
816 template <typename MT = mapped_type>
817 GAIA_NODISCARD typename std::enable_if<is_set, MT const&>::type getSecond() const noexcept {
818 return mData.second;
819 }
820
821 void
822 swap(DataNode<M, true>& o) noexcept(noexcept(std::declval<value_type>().swap(std::declval<value_type>()))) {
823 mData.swap(o.mData);
824 }
825
826 private:
827 value_type mData;
828 };
829
830 // big object: allocate on heap.
831 template <typename M>
832 class DataNode<M, false> {
833 public:
834 template <typename... Args>
835 explicit DataNode(M& map, Args&&... args): mData(map.allocate()) {
836 ::new (static_cast<void*>(mData)) value_type(GAIA_FWD(args)...);
837 }
838
839 DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, false>&& n) noexcept: mData(GAIA_MOV(n.mData)) {}
840
841 void destroy(M& map) noexcept {
842 // don't deallocate, just put it into list of datapool.
843 mData->~value_type();
844 map.deallocate(mData);
845 }
846
847 void destroyDoNotDeallocate() noexcept {
848 mData->~value_type();
849 }
850
851 value_type const* operator->() const noexcept {
852 return mData;
853 }
854
855 value_type* operator->() noexcept {
856 return mData;
857 }
858
859 const value_type& operator*() const {
860 return *mData;
861 }
862
863 value_type& operator*() {
864 return *mData;
865 }
866
867 template <typename VT = value_type>
868 GAIA_NODISCARD typename std::enable_if<is_map, typename VT::first_type&>::type getFirst() noexcept {
869 return mData->first;
870 }
871 template <typename VT = value_type>
872 GAIA_NODISCARD typename std::enable_if<is_set, VT&>::type getFirst() noexcept {
873 return *mData;
874 }
875
876 template <typename VT = value_type>
877 GAIA_NODISCARD typename std::enable_if<is_map, typename VT::first_type const&>::type getFirst() const noexcept {
878 return mData->first;
879 }
880 template <typename VT = value_type>
881 GAIA_NODISCARD typename std::enable_if<is_set, VT const&>::type getFirst() const noexcept {
882 return *mData;
883 }
884
885 template <typename MT = mapped_type>
886 GAIA_NODISCARD typename std::enable_if<is_map, MT&>::type getSecond() noexcept {
887 return mData->second;
888 }
889
890 template <typename MT = mapped_type>
891 GAIA_NODISCARD typename std::enable_if<is_map, MT const&>::type getSecond() const noexcept {
892 return mData->second;
893 }
894
895 void swap(DataNode<M, false>& o) noexcept {
896 using gaia::core::swap;
897 swap(mData, o.mData);
898 }
899
900 private:
901 value_type* mData;
902 };
903
904 using Node = DataNode<Self, IsFlat>;
905
906 // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required)
907 GAIA_NODISCARD key_type const& getFirstConst(Node const& n) const noexcept {
908 return n.getFirst();
909 }
910
911 // in case we have void mapped_type, we are not using a pair, thus we just route k through.
912 // No need to disable this because it's just not used if not applicable.
913 GAIA_NODISCARD key_type const& getFirstConst(key_type const& k) const noexcept {
914 return k;
915 }
916
917 // in case we have non-void mapped_type, we have a standard robin_hood::pair
918 template <typename Q = mapped_type>
919 GAIA_NODISCARD typename std::enable_if<!std::is_void<Q>::value, key_type const&>::type
920 getFirstConst(value_type const& vt) const noexcept {
921 return vt.first;
922 }
923
924 // Cloner //////////////////////////////////////////////////////////
925
926 template <typename M, bool UseMemcpy>
927 struct Cloner;
928
929 // fast path: Just copy data, without allocating anything.
930 template <typename M>
931 struct Cloner<M, true> {
932 void operator()(M const& source, M& target) const {
933 auto const* const src = reinterpret_cast<uint8_t const*>(source.mKeyVals);
934 auto* tgt = reinterpret_cast<uint8_t*>(target.mKeyVals);
935 auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1);
936 gaia::mem::copy_elements<uint8_t, false>(
937 tgt, src, (uint32_t)target.calcNumBytesTotal(numElementsWithBuffer), 0, 0, 0);
938 }
939 };
940
941 template <typename M>
942 struct Cloner<M, false> {
943 void operator()(M const& s, M& t) const {
944 auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1);
945 gaia::mem::copy_elements<uint8_t, false>(
946 t.mInfo, s.mInfo, (uint32_t)t.calcNumBytesInfo(numElementsWithBuffer), 0, 0, 0);
947
948 for (size_t i = 0; i < numElementsWithBuffer; ++i) {
949 if (t.mInfo[i]) {
950 ::new (static_cast<void*>(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]);
951 }
952 }
953 }
954 };
955
956 // Destroyer ///////////////////////////////////////////////////////
957
958 template <typename M, bool IsFlatAndTrivial>
959 struct Destroyer {};
960
961 template <typename M>
962 struct Destroyer<M, true> {
963 void nodes(M& m) const noexcept {
964 m.mNumElements = 0;
965 }
966
967 void nodesDoNotDeallocate(M& m) const noexcept {
968 m.mNumElements = 0;
969 }
970 };
971
972 template <typename M>
973 struct Destroyer<M, false> {
974 void nodes(M& m) const noexcept {
975 m.mNumElements = 0;
976 // clear also resets mInfo to 0, that's sometimes not necessary.
977 auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1);
978
979 for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) {
980 if (0 != m.mInfo[idx]) {
981 Node& n = m.mKeyVals[idx];
982 n.destroy(m);
983 n.~Node();
984 }
985 }
986 }
987
988 void nodesDoNotDeallocate(M& m) const noexcept {
989 m.mNumElements = 0;
990 // clear also resets mInfo to 0, that's sometimes not necessary.
991 auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1);
992 for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) {
993 if (0 != m.mInfo[idx]) {
994 Node& n = m.mKeyVals[idx];
995 n.destroyDoNotDeallocate();
996 n.~Node();
997 }
998 }
999 }
1000 };
1001
1002 // Iter ////////////////////////////////////////////////////////////
1003
1004 struct fast_forward_tag {};
1005
1006 // generic iterator for both const_iterator and iterator.
1007 template <bool IsConst>
1008 // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions)
1009 class Iter {
1010 private:
1011 using NodePtr = typename std::conditional<IsConst, Node const*, Node*>::type;
1012
1013 public:
1014 using difference_type = std::ptrdiff_t;
1015 using value_type = typename Self::value_type;
1016 using reference = typename std::conditional<IsConst, value_type const&, value_type&>::type;
1017 using pointer = typename std::conditional<IsConst, value_type const*, value_type*>::type;
1018 using iterator_category = gaia::core::forward_iterator_tag;
1019
1020 // default constructed iterator can be compared to itself, but WON'T return true when
1021 // compared to end().
1022 Iter() = default;
1023
1024 // Rule of zero: nothing specified. The conversion constructor is only enabled for
1025 // iterator to const_iterator, so it doesn't accidentally work as a copy ctor.
1026
1027 // Conversion constructor from iterator to const_iterator.
1028 template <bool OtherIsConst, typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
1029 // NOLINTNEXTLINE(hicpp-explicit-conversions)
1030 Iter(Iter<OtherIsConst> const& other) noexcept: mKeyVals(other.mKeyVals), mInfo(other.mInfo) {}
1031
1032 Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept: mKeyVals(valPtr), mInfo(infoPtr) {}
1033
1034 Iter(NodePtr valPtr, uint8_t const* infoPtr, fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept:
1035 mKeyVals(valPtr), mInfo(infoPtr) {
1036 fastForward();
1037 }
1038
1039 template <bool OtherIsConst, typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
1040 Iter& operator=(Iter<OtherIsConst> const& other) noexcept {
1041 mKeyVals = other.mKeyVals;
1042 mInfo = other.mInfo;
1043 return *this;
1044 }
1045
1046 // prefix increment. Undefined behavior if we are at end()!
1047 Iter& operator++() noexcept {
1048 ++mInfo;
1049 ++mKeyVals;
1050 fastForward();
1051 return *this;
1052 }
1053
1054 Iter operator++(int) noexcept {
1055 Iter tmp = *this;
1056 ++(*this);
1057 return tmp;
1058 }
1059
1060 reference operator*() const {
1061 return **mKeyVals;
1062 }
1063
1064 pointer operator->() const {
1065 return &**mKeyVals;
1066 }
1067
1068 template <bool O>
1069 bool operator==(Iter<O> const& o) const noexcept {
1070 return mKeyVals == o.mKeyVals;
1071 }
1072
1073 template <bool O>
1074 bool operator!=(Iter<O> const& o) const noexcept {
1075 return mKeyVals != o.mKeyVals;
1076 }
1077
1078 private:
1079 // fast forward to the next non-free info byte
1080 // I've tried a few variants that don't depend on intrinsics, but unfortunately they are
1081 // quite a bit slower than this one. So I've reverted that change again. See map_benchmark.
1082 void fastForward() noexcept {
1083 size_t n = 0;
1084 while (0U == (n = detail::unaligned_load<size_t>(mInfo))) {
1085 GAIA_MSVC_WARNING_PUSH()
1086 GAIA_MSVC_WARNING_DISABLE(6305)
1087 mInfo += sizeof(size_t);
1088 mKeyVals += sizeof(size_t);
1089 GAIA_MSVC_WARNING_POP()
1090 }
1091#if defined(ROBIN_HOOD_DISABLE_INTRINSICS)
1092 // we know for certain that within the next 8 bytes we'll find a non-zero one.
1093 if GAIA_UNLIKELY (0U == detail::unaligned_load<uint32_t>(mInfo)) {
1094 mInfo += 4;
1095 mKeyVals += 4;
1096 }
1097 if GAIA_UNLIKELY (0U == detail::unaligned_load<uint16_t>(mInfo)) {
1098 mInfo += 2;
1099 mKeyVals += 2;
1100 }
1101 if GAIA_UNLIKELY (0U == *mInfo) {
1102 mInfo += 1;
1103 mKeyVals += 1;
1104 }
1105#else
1106 #if GAIA_LITTLE_ENDIAN
1107 auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
1108 #else
1109 auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
1110 #endif
1111 mInfo += inc;
1112 mKeyVals += inc;
1113#endif
1114 }
1115
1116 friend class Table<IsFlat, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
1117 NodePtr mKeyVals{nullptr};
1118 uint8_t const* mInfo{nullptr};
1119 };
1120
1122
1123 // highly performance relevant code.
1124 // Lower bits are used for indexing into the array (2^n size)
1125 // The upper 1-5 bits need to be a reasonable good hash, to save comparisons.
1126 template <typename HashKey>
1127 void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const {
1128 auto h = static_cast<uint64_t>(WHash::operator()(key));
1129
1130 // direct_hash_key is expected to be a proper hash. No additional hash tricks are required
1131 using HashKeyRaw = std::decay_t<HashKey>;
1132 if constexpr (!gaia::core::is_direct_hash_key_v<HashKeyRaw>) {
1133 // In addition to whatever hash is used, add another mul & shift so we get better hashing.
1134 // This serves as a bad hash prevention, if the given data is
1135 // badly mixed.
1136 h *= mHashMultiplier;
1137 h ^= h >> 33U;
1138 }
1139
1140 // the lower InitialInfoNumBits are reserved for info.
1141 *info = mInfoInc + static_cast<InfoType>(h >> mInfoHashShift);
1142 *idx = (static_cast<size_t>(h)) & mMask;
1143 }
1144
1145 // forwards the index by one, wrapping around at the end
1146 void next(InfoType* info, size_t* idx) const noexcept {
1147 *idx = *idx + 1;
1148 *info += mInfoInc;
1149 }
1150
1151 void nextWhileLess(InfoType* info, size_t* idx) const noexcept {
1152 // unrolling this by hand did not bring any speedups.
1153 while (*info < mInfo[*idx]) {
1154 next(info, idx);
1155 }
1156 }
1157
1158 // Shift everything up by one element. Tries to move stuff around.
1159 void shiftUp(size_t startIdx, size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
1160 auto idx = startIdx;
1161 ::new (static_cast<void*>(mKeyVals + idx)) Node(GAIA_MOV(mKeyVals[idx - 1]));
1162 while (--idx != insertion_idx) {
1163 mKeyVals[idx] = GAIA_MOV(mKeyVals[idx - 1]);
1164 }
1165
1166 idx = startIdx;
1167 while (idx != insertion_idx) {
1168 mInfo[idx] = static_cast<uint8_t>(mInfo[idx - 1] + mInfoInc);
1169 if GAIA_UNLIKELY (mInfo[idx] + mInfoInc > 0xFF) {
1170 mMaxNumElementsAllowed = 0;
1171 }
1172 --idx;
1173 }
1174 }
1175
1176 void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
1177 // until we find one that is either empty or has zero offset.
1178 // TODO(martinus) we don't need to move everything, just the last one for the same
1179 // bucket.
1180 mKeyVals[idx].destroy(*this);
1181
1182 // until we find one that is either empty or has zero offset.
1183 while (mInfo[idx + 1] >= 2 * mInfoInc) {
1184 mInfo[idx] = static_cast<uint8_t>(mInfo[idx + 1] - mInfoInc);
1185 mKeyVals[idx] = GAIA_MOV(mKeyVals[idx + 1]);
1186 ++idx;
1187 }
1188
1189 mInfo[idx] = 0;
1190 // don't destroy, we've moved it
1191 // mKeyVals[idx].destroy(*this);
1192 mKeyVals[idx].~Node();
1193 }
1194
1195 // copy of find(), except that it returns iterator instead of const_iterator.
1196 template <typename Other>
1197 GAIA_NODISCARD size_t findIdx(Other const& key) const {
1198 size_t idx{};
1199 InfoType info{};
1200 keyToIdx(key, &idx, &info);
1201
1202 do {
1203 // unrolling this twice gives a bit of a speedup. More unrolling did not help.
1204 if (info == mInfo[idx]) {
1205 if GAIA_LIKELY (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
1206 return idx;
1207 }
1208 }
1209 next(&info, &idx);
1210 if (info == mInfo[idx]) {
1211 if GAIA_LIKELY (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
1212 return idx;
1213 }
1214 }
1215 next(&info, &idx);
1216 } while (info <= mInfo[idx]);
1217
1218 // nothing found!
1219 return mMask == 0 ? 0
1220 : static_cast<size_t>(
1221 gaia::core::distance(mKeyVals, reinterpret_cast_no_cast_align_warning<Node*>(mInfo)));
1222 }
1223
1224 void cloneData(const Table& o) {
1225 Cloner<Table, IsFlat && ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(Node)>()(o, *this);
1226 }
1227
1228 // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized.
1229 // @return True on success, false if something went wrong
1230 void insert_move(Node&& keyval) {
1231 // we don't retry, fail if overflowing
1232 // don't need to check max num elements
1233 if (0 == mMaxNumElementsAllowed && !try_increase_info()) {
1234 throwOverflowError();
1235 }
1236
1237 size_t idx{};
1238 InfoType info{};
1239 keyToIdx(keyval.getFirst(), &idx, &info);
1240
1241 // skip forward. Use <= because we are certain that the element is not there.
1242 while (info <= mInfo[idx]) {
1243 idx = idx + 1;
1244 info += mInfoInc;
1245 }
1246
1247 // key not found, so we are now exactly where we want to insert it.
1248 auto const insertion_idx = idx;
1249 auto const insertion_info = static_cast<uint8_t>(info);
1250 if GAIA_UNLIKELY (insertion_info + mInfoInc > 0xFF) {
1251 mMaxNumElementsAllowed = 0;
1252 }
1253
1254 // find an empty spot
1255 while (0 != mInfo[idx]) {
1256 next(&info, &idx);
1257 }
1258
1259 auto& l = mKeyVals[insertion_idx];
1260 if (idx == insertion_idx) {
1261 ::new (static_cast<void*>(&l)) Node(GAIA_MOV(keyval));
1262 } else {
1263 shiftUp(idx, insertion_idx);
1264 l = GAIA_MOV(keyval);
1265 }
1266
1267 // put at empty spot
1268 mInfo[insertion_idx] = insertion_info;
1269
1270 ++mNumElements;
1271 }
1272
1273 public:
1274 using iterator = Iter<false>;
1275 using const_iterator = Iter<true>;
1276
1277 Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())): WHash(), WKeyEqual() {
1278 ROBIN_HOOD_TRACE("%p", this);
1279 GAIA_ASSERT(gaia::CheckEndianess());
1280 }
1281
1282 // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert.
1283 // This tremendously speeds up ctor & dtor of a map that never receives an element. The
1284 // penalty is payed at the first insert, and not before. Lookup of this empty map works
1285 // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the
1286 // standard, but we can ignore it.
1287 explicit Table(
1288 size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{},
1289 const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))):
1290 WHash(h), WKeyEqual(equal) {
1291 ROBIN_HOOD_TRACE("%p", this);
1292 GAIA_ASSERT(gaia::CheckEndianess());
1293 }
1294
1295 template <typename Iter>
1296 Table(
1297 Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{},
1298 const KeyEqual& equal = KeyEqual{}): WHash(h), WKeyEqual(equal) {
1299 ROBIN_HOOD_TRACE("%p", this);
1300 GAIA_ASSERT(gaia::CheckEndianess());
1301 insert(first, last);
1302 }
1303
1304 Table(
1305 std::initializer_list<value_type> initlist, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0,
1306 const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}): WHash(h), WKeyEqual(equal) {
1307 ROBIN_HOOD_TRACE("%p", this);
1308 GAIA_ASSERT(gaia::CheckEndianess());
1309 insert(initlist.begin(), initlist.end());
1310 }
1311
1312 Table(Table&& o) noexcept:
1313 WHash(GAIA_MOV(static_cast<WHash&>(o))), WKeyEqual(GAIA_MOV(static_cast<WKeyEqual&>(o))),
1314 DataPool(GAIA_MOV(static_cast<DataPool&>(o))) {
1315 ROBIN_HOOD_TRACE("%p", this);
1316 if (o.mMask) {
1317 mHashMultiplier = GAIA_MOV(o.mHashMultiplier);
1318 mKeyVals = GAIA_MOV(o.mKeyVals);
1319 mInfo = GAIA_MOV(o.mInfo);
1320 mNumElements = GAIA_MOV(o.mNumElements);
1321 mMask = GAIA_MOV(o.mMask);
1322 mMaxNumElementsAllowed = GAIA_MOV(o.mMaxNumElementsAllowed);
1323 mInfoInc = GAIA_MOV(o.mInfoInc);
1324 mInfoHashShift = GAIA_MOV(o.mInfoHashShift);
1325 // set other's mask to 0 so its destructor won't do anything
1326 o.init();
1327 }
1328 }
1329
1330 Table& operator=(Table&& o) noexcept {
1331 ROBIN_HOOD_TRACE("%p", this);
1332 if (&o != this) {
1333 if (o.mMask) {
1334 // only move stuff if the other map actually has some data
1335 destroy();
1336 mHashMultiplier = GAIA_MOV(o.mHashMultiplier);
1337 mKeyVals = GAIA_MOV(o.mKeyVals);
1338 mInfo = GAIA_MOV(o.mInfo);
1339 mNumElements = GAIA_MOV(o.mNumElements);
1340 mMask = GAIA_MOV(o.mMask);
1341 mMaxNumElementsAllowed = GAIA_MOV(o.mMaxNumElementsAllowed);
1342 mInfoInc = GAIA_MOV(o.mInfoInc);
1343 mInfoHashShift = GAIA_MOV(o.mInfoHashShift);
1344 WHash::operator=(GAIA_MOV(static_cast<WHash&>(o)));
1345 WKeyEqual::operator=(GAIA_MOV(static_cast<WKeyEqual&>(o)));
1346 DataPool::operator=(GAIA_MOV(static_cast<DataPool&>(o)));
1347
1348 o.init();
1349
1350 } else {
1351 // nothing in the other map => just clear us.
1352 clear();
1353 }
1354 }
1355 return *this;
1356 }
1357
1358 Table(const Table& o):
1359 WHash(static_cast<const WHash&>(o)), WKeyEqual(static_cast<const WKeyEqual&>(o)),
1360 DataPool(static_cast<const DataPool&>(o)) {
1361 ROBIN_HOOD_TRACE("%p", this);
1362 if (!o.empty()) {
1363 // not empty: create an exact copy. it is also possible to just iterate through all
1364 // elements and insert them, but copying is probably faster.
1365
1366 auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1);
1367 auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
1368
1369 ROBIN_HOOD_LOG("gaia::mem::mem_alloc %zu = calcNumBytesTotal(%zu)", numBytesTotal, numElementsWithBuffer);
1370 mHashMultiplier = o.mHashMultiplier;
1371 mKeyVals = static_cast<Node*>(detail::assertNotNull<std::bad_alloc>(gaia::mem::mem_alloc(numBytesTotal)));
1372 // no need for calloc because clonData does memcpy
1373 mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
1374 mNumElements = o.mNumElements;
1375 mMask = o.mMask;
1376 mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
1377 mInfoInc = o.mInfoInc;
1378 mInfoHashShift = o.mInfoHashShift;
1379 cloneData(o);
1380 }
1381 }
1382
1383 // Creates a copy of the given map. Copy constructor of each entry is used.
1384 // Not sure why clang-tidy thinks this doesn't handle self assignment, it does
1385 // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
1386 Table& operator=(Table const& o) {
1387 ROBIN_HOOD_TRACE("%p", this);
1388 if (&o == this) {
1389 // prevent assigning of itself
1390 return *this;
1391 }
1392
1393 // we keep using the old allocator and not assign the new one, because we want to keep
1394 // the memory available. when it is the same size.
1395 if (o.empty()) {
1396 if (0 == mMask) {
1397 // nothing to do, we are empty too
1398 return *this;
1399 }
1400
1401 // not empty: destroy what we have there
1402 // clear also resets mInfo to 0, that's sometimes not necessary.
1403 destroy();
1404 init();
1405 WHash::operator=(static_cast<const WHash&>(o));
1406 WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
1407 DataPool::operator=(static_cast<DataPool const&>(o));
1408
1409 return *this;
1410 }
1411
1412 // clean up old stuff
1413 Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
1414
1415 if (mMask != o.mMask) {
1416 // no luck: we don't have the same array size allocated, so we need to realloc.
1417 if (0 != mMask) {
1418 // only deallocate if we actually have data!
1419 ROBIN_HOOD_LOG("gaia::mem::mem_free");
1420 gaia::mem::mem_free(mKeyVals);
1421 }
1422
1423 auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1);
1424 auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
1425 ROBIN_HOOD_LOG("gaia::mem::mem_alloc %zu = calcNumBytesTotal(%zu)", numBytesTotal, numElementsWithBuffer);
1426 mKeyVals = static_cast<Node*>(detail::assertNotNull<std::bad_alloc>(gaia::mem::mem_alloc(numBytesTotal)));
1427
1428 // no need for calloc here because cloneData performs a memcpy.
1429 mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
1430 // sentinel is set in cloneData
1431 }
1432 WHash::operator=(static_cast<const WHash&>(o));
1433 WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
1434 DataPool::operator=(static_cast<DataPool const&>(o));
1435 mHashMultiplier = o.mHashMultiplier;
1436 mNumElements = o.mNumElements;
1437 mMask = o.mMask;
1438 mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
1439 mInfoInc = o.mInfoInc;
1440 mInfoHashShift = o.mInfoHashShift;
1441 cloneData(o);
1442
1443 return *this;
1444 }
1445
1446 // Swaps everything between the two maps.
1447 void swap(Table& o) {
1448 ROBIN_HOOD_TRACE("%p", this);
1449 using gaia::core::swap;
1450 swap(o, *this);
1451 }
1452
1453 // Clears all data, without resizing.
1454 void clear() {
1455 ROBIN_HOOD_TRACE("%p", this);
1456 if (empty()) {
1457 // don't do anything! also important because we don't want to write to
1458 // DummyInfoByte::b, even though we would just write 0 to it.
1459 return;
1460 }
1461
1462 Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
1463
1464 auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
1465 // clear everything, then set the sentinel again
1466 uint8_t const z = 0;
1467 gaia::core::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z);
1468 mInfo[numElementsWithBuffer] = 1;
1469
1470 mInfoInc = InitialInfoInc;
1471 mInfoHashShift = InitialInfoHashShift;
1472 }
1473
1474 // Destroys the map and all it's contents.
1475 ~Table() {
1476 ROBIN_HOOD_TRACE("%p", this);
1477 destroy();
1478 }
1479
1480 // Checks if both tables contain the same entries. Order is irrelevant.
1481 bool operator==(const Table& other) const {
1482 ROBIN_HOOD_TRACE("%p", this);
1483 if (other.size() != size()) {
1484 return false;
1485 }
1486 for (auto const& otherEntry: other) {
1487 if (!has(otherEntry)) {
1488 return false;
1489 }
1490 }
1491
1492 return true;
1493 }
1494
1495 bool operator!=(const Table& other) const {
1496 ROBIN_HOOD_TRACE("%p", this);
1497 return !operator==(other);
1498 }
1499
1500 template <typename Q = mapped_type>
1501 typename std::enable_if<!std::is_void<Q>::value, Q&>::type operator[](const key_type& key) {
1502 ROBIN_HOOD_TRACE("%p", this);
1503 auto idxAndState = insertKeyPrepareEmptySpot(key);
1504 switch (idxAndState.second) {
1505 case InsertionState::key_found:
1506 break;
1507
1508 case InsertionState::new_node:
1509 ::new (static_cast<void*>(&mKeyVals[idxAndState.first]))
1510 Node(*this, std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple());
1511 break;
1512
1513 case InsertionState::overwrite_node:
1514 mKeyVals[idxAndState.first] =
1515 Node(*this, std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple());
1516 break;
1517
1518 case InsertionState::overflow_error:
1519 throwOverflowError();
1520 }
1521
1522 return mKeyVals[idxAndState.first].getSecond();
1523 }
1524
1525 template <typename Q = mapped_type>
1526 typename std::enable_if<!std::is_void<Q>::value, Q&>::type operator[](key_type&& key) {
1527 ROBIN_HOOD_TRACE("%p", this);
1528 auto idxAndState = insertKeyPrepareEmptySpot(key);
1529 switch (idxAndState.second) {
1530 case InsertionState::key_found:
1531 break;
1532
1533 case InsertionState::new_node:
1534 ::new (static_cast<void*>(&mKeyVals[idxAndState.first]))
1535 Node(*this, std::piecewise_construct, std::forward_as_tuple(GAIA_MOV(key)), std::forward_as_tuple());
1536 break;
1537
1538 case InsertionState::overwrite_node:
1539 mKeyVals[idxAndState.first] =
1540 Node(*this, std::piecewise_construct, std::forward_as_tuple(GAIA_MOV(key)), std::forward_as_tuple());
1541 break;
1542
1543 case InsertionState::overflow_error:
1544 throwOverflowError();
1545 }
1546
1547 return mKeyVals[idxAndState.first].getSecond();
1548 }
1549
1550 template <typename Iter>
1551 void insert(Iter first, Iter last) {
1552 for (; first != last; ++first) {
1553 // value_type ctor needed because this might be called with std::pair's
1554 insert(value_type(*first));
1555 }
1556 }
1557
1558 void insert(std::initializer_list<value_type> ilist) {
1559 for (auto&& vt: ilist) {
1560 insert(GAIA_MOV(vt));
1561 }
1562 }
1563
1564 template <typename... Args>
1565 std::pair<iterator, bool> emplace(Args&&... args) {
1566 ROBIN_HOOD_TRACE("%p", this);
1567 Node n{*this, GAIA_FWD(args)...};
1568 auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n));
1569 switch (idxAndState.second) {
1570 case InsertionState::key_found:
1571 n.destroy(*this);
1572 break;
1573
1574 case InsertionState::new_node:
1575 ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(*this, GAIA_MOV(n));
1576 break;
1577
1578 case InsertionState::overwrite_node:
1579 mKeyVals[idxAndState.first] = GAIA_MOV(n);
1580 break;
1581
1582 case InsertionState::overflow_error:
1583 n.destroy(*this);
1584 throwOverflowError();
1585 break;
1586 }
1587
1588 return std::make_pair(
1589 iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
1590 InsertionState::key_found != idxAndState.second);
1591 }
1592
1593 template <typename... Args>
1594 iterator emplace_hint(const_iterator position, Args&&... args) {
1595 (void)position;
1596 return emplace(GAIA_FWD(args)...).first;
1597 }
1598
1599 template <typename... Args>
1600 std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
1601 return try_emplace_impl(key, GAIA_FWD(args)...);
1602 }
1603
1604 template <typename... Args>
1605 std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
1606 return try_emplace_impl(GAIA_MOV(key), GAIA_FWD(args)...);
1607 }
1608
1609 template <typename... Args>
1610 iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) {
1611 (void)hint;
1612 return try_emplace_impl(key, GAIA_FWD(args)...).first;
1613 }
1614
1615 template <typename... Args>
1616 iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) {
1617 (void)hint;
1618 return try_emplace_impl(GAIA_MOV(key), GAIA_FWD(args)...).first;
1619 }
1620
1621 template <typename Mapped>
1622 std::pair<iterator, bool> insert_or_assign(const key_type& key, Mapped&& obj) {
1623 return insertOrAssignImpl(key, GAIA_FWD(obj));
1624 }
1625
1626 template <typename Mapped>
1627 std::pair<iterator, bool> insert_or_assign(key_type&& key, Mapped&& obj) {
1628 return insertOrAssignImpl(GAIA_MOV(key), GAIA_FWD(obj));
1629 }
1630
1631 template <typename Mapped>
1632 iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) {
1633 (void)hint;
1634 return insertOrAssignImpl(key, GAIA_FWD(obj)).first;
1635 }
1636
1637 template <typename Mapped>
1638 iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) {
1639 (void)hint;
1640 return insertOrAssignImpl(GAIA_MOV(key), GAIA_FWD(obj)).first;
1641 }
1642
1643 std::pair<iterator, bool> insert(const value_type& keyval) {
1644 ROBIN_HOOD_TRACE("%p", this);
1645 return emplace(keyval);
1646 }
1647
1648 iterator insert(const_iterator hint, const value_type& keyval) {
1649 (void)hint;
1650 return emplace(keyval).first;
1651 }
1652
1653 std::pair<iterator, bool> insert(value_type&& keyval) {
1654 return emplace(GAIA_MOV(keyval));
1655 }
1656
1657 iterator insert(const_iterator hint, value_type&& keyval) {
1658 (void)hint;
1659 return emplace(GAIA_MOV(keyval)).first;
1660 }
1661
1662 // Returns 1 if key is found, 0 otherwise.
1663 GAIA_NODISCARD size_t count(const key_type& key) const {
1664 ROBIN_HOOD_TRACE("%p", this);
1665 auto kv = mKeyVals + findIdx(key);
1666 if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
1667 return 1;
1668 }
1669 return 0;
1670 }
1671
1672 template <typename OtherKey, typename Self_ = Self>
1673 GAIA_NODISCARD typename std::enable_if<Self_::is_transparent, size_t>::type count(const OtherKey& key) const {
1674 ROBIN_HOOD_TRACE("%p", this);
1675 auto kv = mKeyVals + findIdx(key);
1676 if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
1677 return 1;
1678 }
1679 return 0;
1680 }
1681
1682 GAIA_NODISCARD bool contains(const key_type& key) const {
1683 return 1U == count(key);
1684 }
1685
1686 template <typename OtherKey, typename Self_ = Self>
1687 GAIA_NODISCARD typename std::enable_if<Self_::is_transparent, bool>::type contains(const OtherKey& key) const {
1688 return 1U == count(key);
1689 }
1690
1691 // Returns a reference to the value found for key.
1692 // Throws std::out_of_range if element cannot be found
1693 template <typename Q = mapped_type>
1694 GAIA_NODISCARD typename std::enable_if<!std::is_void<Q>::value, Q&>::type at(key_type const& key) {
1695 ROBIN_HOOD_TRACE("%p", this);
1696 auto kv = mKeyVals + findIdx(key);
1697 if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
1698 doThrow<ROBIN_HOOD_STD_OUT_OF_RANGE>("key not found");
1699 }
1700 return kv->getSecond();
1701 }
1702
1703 // Returns a reference to the value found for key.
1704 // Throws std::out_of_range if element cannot be found
1705 template <typename Q = mapped_type>
1706 GAIA_NODISCARD typename std::enable_if<!std::is_void<Q>::value, Q const&>::type at(key_type const& key) const {
1707 ROBIN_HOOD_TRACE("%p", this);
1708 auto kv = mKeyVals + findIdx(key);
1709 if (kv == reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
1710 doThrow<ROBIN_HOOD_STD_OUT_OF_RANGE>("key not found");
1711 }
1712 return kv->getSecond();
1713 }
1714
1715 GAIA_NODISCARD const_iterator find(const key_type& key) const {
1716 ROBIN_HOOD_TRACE("%p", this);
1717 const size_t idx = findIdx(key);
1718 return const_iterator{mKeyVals + idx, mInfo + idx};
1719 }
1720
1721 template <typename OtherKey>
1722 GAIA_NODISCARD const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const {
1723 ROBIN_HOOD_TRACE("%p", this);
1724 const size_t idx = findIdx(key);
1725 return const_iterator{mKeyVals + idx, mInfo + idx};
1726 }
1727
1728 template <typename OtherKey, typename Self_ = Self>
1729 GAIA_NODISCARD typename std::enable_if<Self_::is_transparent, const_iterator>::type
1730 find(const OtherKey& key) const {
1731 ROBIN_HOOD_TRACE("%p", this);
1732 const size_t idx = findIdx(key);
1733 return const_iterator{mKeyVals + idx, mInfo + idx};
1734 }
1735
1736 GAIA_NODISCARD iterator find(const key_type& key) {
1737 ROBIN_HOOD_TRACE("%p", this);
1738 const size_t idx = findIdx(key);
1739 return iterator{mKeyVals + idx, mInfo + idx};
1740 }
1741
1742 template <typename OtherKey>
1743 GAIA_NODISCARD iterator find(const OtherKey& key, is_transparent_tag /*unused*/) {
1744 ROBIN_HOOD_TRACE("%p", this);
1745 const size_t idx = findIdx(key);
1746 return iterator{mKeyVals + idx, mInfo + idx};
1747 }
1748
1749 template <typename OtherKey, typename Self_ = Self>
1750 GAIA_NODISCARD typename std::enable_if<Self_::is_transparent, iterator>::type find(const OtherKey& key) {
1751 ROBIN_HOOD_TRACE("%p", this);
1752 const size_t idx = findIdx(key);
1753 return iterator{mKeyVals + idx, mInfo + idx};
1754 }
1755
1756 GAIA_NODISCARD iterator begin() {
1757 ROBIN_HOOD_TRACE("%p", this);
1758 if (empty()) {
1759 return end();
1760 }
1761 return iterator(mKeyVals, mInfo, fast_forward_tag{});
1762 }
1763 GAIA_NODISCARD const_iterator begin() const {
1764 ROBIN_HOOD_TRACE("%p", this);
1765 return cbegin();
1766 }
1767 GAIA_NODISCARD const_iterator cbegin() const {
1768 ROBIN_HOOD_TRACE("%p", this);
1769 if (empty()) {
1770 return cend();
1771 }
1772 return const_iterator(mKeyVals, mInfo, fast_forward_tag{});
1773 }
1774
1775 GAIA_NODISCARD iterator end() {
1776 ROBIN_HOOD_TRACE("%p", this);
1777 // no need to supply valid info pointer: end() must not be dereferenced, and only node
1778 // pointer is compared.
1779 return iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
1780 }
1781 GAIA_NODISCARD const_iterator end() const {
1782 ROBIN_HOOD_TRACE("%p", this);
1783 return cend();
1784 }
1785 GAIA_NODISCARD const_iterator cend() const {
1786 ROBIN_HOOD_TRACE("%p", this);
1787 return const_iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
1788 }
1789
1790 iterator erase(const_iterator pos) {
1791 ROBIN_HOOD_TRACE("%p", this);
1792 // its safe to perform const cast here
1793 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
1794 return erase(iterator{const_cast<Node*>(pos.mKeyVals), const_cast<uint8_t*>(pos.mInfo)});
1795 }
1796
1797 // Erases element at pos, returns iterator to the next element.
1798 iterator erase(iterator pos) {
1799 ROBIN_HOOD_TRACE("%p", this);
1800 // we assume that pos always points to a valid entry, and not end().
1801 auto const idx = static_cast<size_t>(pos.mKeyVals - mKeyVals);
1802
1803 shiftDown(idx);
1804 --mNumElements;
1805
1806 if (*pos.mInfo) {
1807 // we've backward shifted, return this again
1808 return pos;
1809 }
1810
1811 // no backward shift, return next element
1812 return ++pos;
1813 }
1814
1815 size_t erase(const key_type& key) {
1816 ROBIN_HOOD_TRACE("%p", this);
1817 size_t idx{};
1818 InfoType info{};
1819 keyToIdx(key, &idx, &info);
1820
1821 // check while info matches with the source idx
1822 do {
1823 if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
1824 shiftDown(idx);
1825 --mNumElements;
1826 return 1;
1827 }
1828 next(&info, &idx);
1829 } while (info <= mInfo[idx]);
1830
1831 // nothing found to delete
1832 return 0;
1833 }
1834
1835 // reserves space for the specified number of elements. Makes sure the old data fits.
1836 // exactly the same as reserve(c).
1837 void rehash(size_t c) {
1838 // forces a reserve
1839 reserve(c, true);
1840 }
1841
1842 // reserves space for the specified number of elements. Makes sure the old data fits.
1843 // Exactly the same as rehash(c). Use rehash(0) to shrink to fit.
1844 void reserve(size_t c) {
1845 // reserve, but don't force rehash
1846 reserve(c, false);
1847 }
1848
1849 // If possible reallocates the map to a smaller one. This frees the underlying table.
1850 // Does not do anything if load_factor is too large for decreasing the table's size.
1851 void compact() {
1852 ROBIN_HOOD_TRACE("%p", this);
1853 auto newSize = InitialNumElements;
1854 while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) {
1855 newSize *= 2;
1856 }
1857 if GAIA_UNLIKELY (newSize == 0) {
1858 throwOverflowError();
1859 }
1860
1861 ROBIN_HOOD_LOG("newSize > mMask + 1: %zu > %zu + 1", newSize, mMask);
1862
1863 // only actually do anything when the new size is bigger than the old one. This prevents to
1864 // continuously allocate for each reserve() call.
1865 if (newSize < mMask + 1) {
1866 rehashPowerOfTwo(newSize, true);
1867 }
1868 }
1869
1870 constexpr uint32_t bytes() const noexcept {
1871 if constexpr (is_map) {
1872 return sizeof(key_type) + sizeof(value_type);
1873 } else {
1874 return sizeof(key_type);
1875 }
1876 }
1877
1878 template <typename Serializer>
1879 void save(Serializer& s) const {
1880 const auto cnt = (uint32_t)size();
1881 s.save(cnt);
1882
1883 if constexpr (is_map) {
1884 for (auto& p: *this) {
1885 ::gaia::ser::save(s, p.first);
1886 ::gaia::ser::save(s, p.second);
1887 }
1888 } else {
1889 for (auto& p: *this) {
1890 ::gaia::ser::save(s, p);
1891 }
1892 }
1893 }
1894
1895 template <typename Serializer>
1896 void load(Serializer& s) {
1897 // Clear old data
1898 clear();
1899
1900 uint32_t cnt = 0;
1901 s.load(cnt);
1902
1903 if constexpr (is_map) {
1904 for (uint32_t i = 0; i < cnt; ++i) {
1905 typename Table::key_type key;
1906 typename Table::mapped_type value;
1907 ::gaia::ser::load(s, key);
1908 ::gaia::ser::load(s, value);
1909 insert({key, value});
1910 }
1911 } else {
1912 for (uint32_t i = 0; i < cnt; ++i) {
1913 typename Table::key_type key;
1914 ::gaia::ser::load(s, key);
1915 insert(key);
1916 }
1917 }
1918 }
1919
1920 GAIA_NODISCARD size_type capacity() const noexcept {
1921 return calcMaxNumElementsAllowed(mMask);
1922 }
1923
1924 GAIA_NODISCARD size_type size() const noexcept {
1925 ROBIN_HOOD_TRACE("%p", this);
1926 return mNumElements;
1927 }
1928
1929 GAIA_NODISCARD size_type max_size() const noexcept {
1930 ROBIN_HOOD_TRACE("%p", this);
1931 return static_cast<size_type>(-1);
1932 }
1933
1934 GAIA_NODISCARD bool empty() const noexcept {
1935 ROBIN_HOOD_TRACE("%p", this);
1936 return 0 == mNumElements;
1937 }
1938
1939 GAIA_NODISCARD float max_load_factor() const noexcept {
1940 ROBIN_HOOD_TRACE("%p", this);
1941 return MaxLoadFactor100 / 100.0f;
1942 }
1943
1944 // Average number of elements per bucket. Since we allow only 1 per bucket
1945 GAIA_NODISCARD float load_factor() const noexcept {
1946 ROBIN_HOOD_TRACE("%p", this);
1947 return static_cast<float>(size()) / static_cast<float>(mMask + 1);
1948 }
1949
1950 GAIA_NODISCARD size_t mask() const noexcept {
1951 ROBIN_HOOD_TRACE("%p", this);
1952 return mMask;
1953 }
1954
1955 GAIA_NODISCARD size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept {
1956 if GAIA_LIKELY (maxElements <= (size_t(-1) / 100)) {
1957 return maxElements * MaxLoadFactor100 / 100;
1958 }
1959
1960 // we might be a bit inprecise, but since maxElements is quite large that doesn't matter
1961 return (maxElements / 100) * MaxLoadFactor100;
1962 }
1963
1964 GAIA_NODISCARD size_t calcNumBytesInfo(size_t numElements) const noexcept {
1965 // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load
1966 // 64bit types.
1967 return numElements + sizeof(uint64_t);
1968 }
1969
1970 GAIA_NODISCARD size_t calcNumElementsWithBuffer(size_t numElements) const noexcept {
1971 auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements);
1972 return numElements + gaia::core::get_min(maxNumElementsAllowed, (static_cast<size_t>(0xFF)));
1973 }
1974
1975 // calculation only allowed for 2^n values
1976 GAIA_NODISCARD size_t calcNumBytesTotal(size_t numElements) const {
1977#if ROBIN_HOOD(BITNESS) == 64
1978 return (numElements * sizeof(Node)) + calcNumBytesInfo(numElements);
1979#else
1980 // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows.
1981 auto const ne = static_cast<uint64_t>(numElements);
1982 auto const s = static_cast<uint64_t>(sizeof(Node));
1983 auto const infos = static_cast<uint64_t>(calcNumBytesInfo(numElements));
1984
1985 auto const total64 = ne * s + infos;
1986 auto const total = static_cast<size_t>(total64);
1987
1988 if GAIA_UNLIKELY (static_cast<uint64_t>(total) != total64) {
1989 throwOverflowError();
1990 }
1991 return total;
1992#endif
1993 }
1994
1995 private:
1996 template <typename Q = mapped_type>
1997 GAIA_NODISCARD typename std::enable_if<!std::is_void<Q>::value, bool>::type has(const value_type& e) const {
1998 ROBIN_HOOD_TRACE("%p", this);
1999 auto it = find(e.first);
2000 return it != end() && it->second == e.second;
2001 }
2002
2003 template <typename Q = mapped_type>
2004 GAIA_NODISCARD typename std::enable_if<std::is_void<Q>::value, bool>::type has(const value_type& e) const {
2005 ROBIN_HOOD_TRACE("%p", this);
2006 return find(e) != end();
2007 }
2008
2009 void reserve(size_t c, bool forceRehash) {
2010 ROBIN_HOOD_TRACE("%p", this);
2011 auto const minElementsAllowed = gaia::core::get_max(c, mNumElements);
2012 auto newSize = InitialNumElements;
2013 while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) {
2014 newSize *= 2;
2015 }
2016 if GAIA_UNLIKELY (newSize == 0) {
2017 throwOverflowError();
2018 }
2019
2020 ROBIN_HOOD_LOG("newSize > mMask + 1: %zu > %zu + 1", newSize, mMask);
2021
2022 // only actually do anything when the new size is bigger than the old one. This prevents to
2023 // continuously allocate for each reserve() call.
2024 if (forceRehash || newSize > mMask + 1) {
2025 rehashPowerOfTwo(newSize, false);
2026 }
2027 }
2028
2029 // reserves space for at least the specified number of elements.
2030 // only works if numBuckets if power of two
2031 // True on success, false otherwise
2032 void rehashPowerOfTwo(size_t numBuckets, bool forceFree) {
2033 ROBIN_HOOD_TRACE("%p", this);
2034
2035 Node* const oldKeyVals = mKeyVals;
2036 uint8_t const* const oldInfo = mInfo;
2037
2038 const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
2039
2040 // resize operation: move stuff
2041 initData(numBuckets);
2042 if (oldMaxElementsWithBuffer > 1) {
2043 for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) {
2044 if (oldInfo[i] != 0) {
2045 // might throw an exception, which is really bad since we are in the middle of
2046 // moving stuff.
2047 insert_move(GAIA_MOV(oldKeyVals[i]));
2048 // destroy the node but DON'T destroy the data.
2049 oldKeyVals[i].~Node();
2050 }
2051 }
2052
2053 // this check is not necessary as it's guarded by the previous if, but it helps
2054 // silence g++'s overeager "attempt to free a non-heap object 'map'
2055 // [-Werror=free-nonheap-object]" warning.
2056 if (oldKeyVals != reinterpret_cast_no_cast_align_warning<Node*>(&mMask)) {
2057 // don't destroy old data: put it into the pool instead
2058 if (forceFree) {
2059 gaia::mem::mem_free(oldKeyVals);
2060 } else {
2061 DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer));
2062 }
2063 }
2064 }
2065 }
2066
2067 GAIA_NOINLINE void throwOverflowError() const {
2068#if ROBIN_HOOD(HAS_EXCEPTIONS)
2069 throw std::overflow_error("robin_hood::map overflow");
2070#else
2071 abort();
2072#endif
2073 }
2074
2075 template <typename OtherKey, typename... Args>
2076 std::pair<iterator, bool> try_emplace_impl(OtherKey&& key, Args&&... args) {
2077 ROBIN_HOOD_TRACE("%p", this);
2078 auto idxAndState = insertKeyPrepareEmptySpot(key);
2079 switch (idxAndState.second) {
2080 case InsertionState::key_found:
2081 break;
2082
2083 case InsertionState::new_node:
2084 ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(
2085 *this, std::piecewise_construct, std::forward_as_tuple(GAIA_FWD(key)),
2086 std::forward_as_tuple(GAIA_FWD(args)...));
2087 break;
2088
2089 case InsertionState::overwrite_node:
2090 mKeyVals[idxAndState.first] = Node(
2091 *this, std::piecewise_construct, std::forward_as_tuple(GAIA_FWD(key)),
2092 std::forward_as_tuple(GAIA_FWD(args)...));
2093 break;
2094
2095 case InsertionState::overflow_error:
2096 throwOverflowError();
2097 break;
2098 }
2099
2100 return std::make_pair(
2101 iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
2102 InsertionState::key_found != idxAndState.second);
2103 }
2104
2105 template <typename OtherKey, typename Mapped>
2106 std::pair<iterator, bool> insertOrAssignImpl(OtherKey&& key, Mapped&& obj) {
2107 ROBIN_HOOD_TRACE("%p", this);
2108 auto idxAndState = insertKeyPrepareEmptySpot(key);
2109 switch (idxAndState.second) {
2110 case InsertionState::key_found:
2111 mKeyVals[idxAndState.first].getSecond() = GAIA_FWD(obj);
2112 break;
2113
2114 case InsertionState::new_node:
2115 ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(
2116 *this, std::piecewise_construct, std::forward_as_tuple(GAIA_FWD(key)),
2117 std::forward_as_tuple(GAIA_FWD(obj)));
2118 break;
2119
2120 case InsertionState::overwrite_node:
2121 mKeyVals[idxAndState.first] = Node(
2122 *this, std::piecewise_construct, std::forward_as_tuple(GAIA_FWD(key)),
2123 std::forward_as_tuple(GAIA_FWD(obj)));
2124 break;
2125
2126 case InsertionState::overflow_error:
2127 throwOverflowError();
2128 break;
2129 }
2130
2131 return std::make_pair(
2132 iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
2133 InsertionState::key_found != idxAndState.second);
2134 }
2135
2136 void initData(size_t max_elements) {
2137 mNumElements = 0;
2138 mMask = max_elements - 1;
2139 mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements);
2140
2141 auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements);
2142
2143 // malloc & zero mInfo. Faster than calloc everything.
2144 auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
2145
2146 ROBIN_HOOD_LOG("gaia::mem::mem_alloc %zu = calcNumBytesTotal(%zu)", numBytesTotal, numElementsWithBuffer);
2147 mKeyVals = reinterpret_cast<Node*>(detail::assertNotNull<std::bad_alloc>(gaia::mem::mem_alloc(numBytesTotal)));
2148 mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
2149 std::memset(mInfo, 0, numBytesTotal - (numElementsWithBuffer * sizeof(Node)));
2150
2151 // set sentinel
2152 mInfo[numElementsWithBuffer] = 1;
2153
2154 mInfoInc = InitialInfoInc;
2155 mInfoHashShift = InitialInfoHashShift;
2156 }
2157
2158 enum class InsertionState { overflow_error, key_found, new_node, overwrite_node };
2159
2160 // Finds key, and if not already present prepares a spot where to pot the key & value.
2161 // This potentially shifts nodes out of the way, updates mInfo and number of inserted
2162 // elements, so the only operation left to do is create/assign a new node at that spot.
2163 template <typename OtherKey>
2164 std::pair<size_t, InsertionState> insertKeyPrepareEmptySpot(OtherKey&& key) {
2165 for (int i = 0; i < 256; ++i) {
2166 size_t idx{};
2167 InfoType info{};
2168 keyToIdx(key, &idx, &info);
2169 nextWhileLess(&info, &idx);
2170
2171 // while we potentially have a match
2172 while (info == mInfo[idx]) {
2173 if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
2174 // key already exists, do NOT insert.
2175 // see http://en.cppreference.com/w/cpp/container/unordered_map/insert
2176 return std::make_pair(idx, InsertionState::key_found);
2177 }
2178 next(&info, &idx);
2179 }
2180
2181 // unlikely that this evaluates to true
2182 if GAIA_UNLIKELY (mNumElements >= mMaxNumElementsAllowed) {
2183 if (!increase_size()) {
2184 return std::make_pair(size_t(0), InsertionState::overflow_error);
2185 }
2186 continue;
2187 }
2188
2189 // key not found, so we are now exactly where we want to insert it.
2190 auto const insertion_idx = idx;
2191 auto const insertion_info = info;
2192 if GAIA_UNLIKELY (insertion_info + mInfoInc > 0xFF) {
2193 mMaxNumElementsAllowed = 0;
2194 }
2195
2196 // find an empty spot
2197 while (0 != mInfo[idx]) {
2198 next(&info, &idx);
2199 }
2200
2201 if (idx != insertion_idx) {
2202 shiftUp(idx, insertion_idx);
2203 }
2204 // put at empty spot
2205 mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
2206 ++mNumElements;
2207 return std::make_pair(
2208 insertion_idx, idx == insertion_idx ? InsertionState::new_node : InsertionState::overwrite_node);
2209 }
2210
2211 // enough attempts failed, so finally give up.
2212 return std::make_pair(size_t(0), InsertionState::overflow_error);
2213 }
2214
2215 bool try_increase_info() {
2216 ROBIN_HOOD_LOG(
2217 "mInfoInc %zu, numElements=%zu, maxNumElementsAllowed=%zu", mInfoInc, mNumElements,
2218 calcMaxNumElementsAllowed(mMask + 1));
2219 if (mInfoInc <= 2) {
2220 // need to be > 2 so that shift works (otherwise undefined behavior!)
2221 return false;
2222 }
2223 // we got space left, try to make info smaller
2224 mInfoInc = static_cast<uint8_t>(mInfoInc >> 1U);
2225
2226 // remove one bit of the hash, leaving more space for the distance info.
2227 // This is extremely fast because we can operate on 8 bytes at once.
2228 ++mInfoHashShift;
2229 auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
2230
2231 for (size_t i = 0; i < numElementsWithBuffer; i += 8) {
2232 auto val = unaligned_load<uint64_t>(mInfo + i);
2233 val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f);
2234 memcpy(mInfo + i, &val, sizeof(val));
2235 }
2236 // update sentinel, which might have been cleared out!
2237 mInfo[numElementsWithBuffer] = 1;
2238
2239 mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
2240 return true;
2241 }
2242
2243 // True if resize was possible, false otherwise
2244 bool increase_size() {
2245 // nothing allocated yet? just allocate InitialNumElements
2246 if (0 == mMask) {
2247 initData(InitialNumElements);
2248 return true;
2249 }
2250
2251 auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
2252 if (mNumElements < maxNumElementsAllowed && try_increase_info()) {
2253 return true;
2254 }
2255
2256 ROBIN_HOOD_LOG(
2257 "mNumElements=%zu, maxNumElementsAllowed=%zu, load=%f", mNumElements, maxNumElementsAllowed,
2258 (static_cast<double>(mNumElements) * 100.0 / (static_cast<double>(mMask) + 1)))
2259
2260 if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) {
2261 // we have to resize, even though there would still be plenty of space left!
2262 // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case
2263 // we have to rehash a few times
2264 nextHashMultiplier();
2265 rehashPowerOfTwo(mMask + 1, true);
2266 } else {
2267 // we've reached the capacity of the map, so the hash seems to work nice. Keep using it.
2268 rehashPowerOfTwo((mMask + 1) * 2, false);
2269 }
2270 return true;
2271 }
2272
2273 void nextHashMultiplier() {
2274 // adding an *even* number, so that the multiplier will always stay odd. This is necessary
2275 // so that the hash stays a mixing function (and thus doesn't have any information loss).
2276 mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54);
2277 }
2278
2279 void destroy() {
2280 if (0 == mMask) {
2281 // don't deallocate!
2282 return;
2283 }
2284
2285 Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}.nodesDoNotDeallocate(*this);
2286
2287 // This protection against not deleting mMask shouldn't be needed as it's sufficiently
2288 // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise
2289 // reports a compile error: attempt to free a non-heap object 'fm'
2290 // [-Werror=free-nonheap-object]
2291 if (mKeyVals != reinterpret_cast_no_cast_align_warning<Node*>(&mMask)) {
2292 ROBIN_HOOD_LOG("gaia::mem::mem_free");
2293 gaia::mem::mem_free(mKeyVals);
2294 }
2295 }
2296
2297 void init() noexcept {
2298 mKeyVals = reinterpret_cast_no_cast_align_warning<Node*>(&mMask);
2299 mInfo = reinterpret_cast<uint8_t*>(&mMask);
2300 mNumElements = 0;
2301 mMask = 0;
2302 mMaxNumElementsAllowed = 0;
2303 mInfoInc = InitialInfoInc;
2304 mInfoHashShift = InitialInfoHashShift;
2305 }
2306
2307 // members are sorted so no padding occurs
2308 uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8
2309 Node* mKeyVals = reinterpret_cast_no_cast_align_warning<Node*>(&mMask); // 8 byte 16
2310 uint8_t* mInfo = reinterpret_cast<uint8_t*>(&mMask); // 8 byte 24
2311 size_t mNumElements = 0; // 8 byte 32
2312 size_t mMask = 0; // 8 byte 40
2313 size_t mMaxNumElementsAllowed = 0; // 8 byte 48
2314 InfoType mInfoInc = InitialInfoInc; // 4 byte 52
2315 InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56
2316 // 16 byte 56 if NodeAllocator
2317 };
2318
2319 } // namespace detail
2320
2321 // map
2322
2323 template <
2324 typename Key, typename T, typename Hash = hash<Key>, typename KeyEqual = gaia::core::equal_to<Key>,
2325 size_t MaxLoadFactor100 = 80>
2327
2328 template <
2329 typename Key, typename T, typename Hash = hash<Key>, typename KeyEqual = gaia::core::equal_to<Key>,
2330 size_t MaxLoadFactor100 = 80>
2332
2333 template <
2334 typename Key, typename T, typename Hash = hash<Key>, typename KeyEqual = gaia::core::equal_to<Key>,
2335 size_t MaxLoadFactor100 = 80>
2337 sizeof(robin_hood::pair<Key, T>) <= sizeof(size_t) * 6 &&
2338 std::is_nothrow_move_constructible<robin_hood::pair<Key, T>>::value &&
2339 std::is_nothrow_move_assignable<robin_hood::pair<Key, T>>::value,
2340 MaxLoadFactor100, Key, T, Hash, KeyEqual>;
2341
2342 // set
2343
2344 template <
2345 typename Key, typename Hash = hash<Key>, typename KeyEqual = gaia::core::equal_to<Key>,
2346 size_t MaxLoadFactor100 = 80>
2348
2349 template <
2350 typename Key, typename Hash = hash<Key>, typename KeyEqual = gaia::core::equal_to<Key>,
2351 size_t MaxLoadFactor100 = 80>
2353
2354 template <
2355 typename Key, typename Hash = hash<Key>, typename KeyEqual = gaia::core::equal_to<Key>,
2356 size_t MaxLoadFactor100 = 80>
2358 sizeof(Key) <= sizeof(size_t) * 6 && std::is_nothrow_move_constructible<Key>::value &&
2359 std::is_nothrow_move_assignable<Key>::value,
2360 MaxLoadFactor100, Key, void, Hash, KeyEqual>;
2361
2362} // namespace robin_hood
2363
2364#endif
Definition robin_hood.h:226
Definition robin_hood.h:720
Definition utility.h:959
Definition iterator.h:12
Definition robin_hood.h:387
Definition robin_hood.h:675
Definition robin_hood.h:681
Definition robin_hood.h:185
Definition robin_hood.h:667
Definition robin_hood.h:405
Definition robin_hood.h:585
Definition robin_hood.h:412
Definition robin_hood.h:418