IOSS 2.0
Loading...
Searching...
No Matches
robin_hash.h
Go to the documentation of this file.
1/**
2 * MIT License
3 *
4 * Copyright (c) 2017, 2023 Thibaut Goetghebuer-Planchon <tessil@gmx.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24#ifndef TSL_ROBIN_HASH_H
25#define TSL_ROBIN_HASH_H
26
27#include <algorithm>
28#include <cassert>
29#include <cmath>
30#include <cstddef>
31#include <cstdint>
32#include <exception>
33#include <iterator>
34#include <limits>
35#include <memory>
36#include <new>
37#include <stdexcept>
38#include <tuple>
39#include <type_traits>
40#include <utility>
41#include <vector>
42
43// NOLINTBEGIN
44#include "robin_growth_policy.h"
45
47
48 template <typename T> struct make_void
49 {
50 using type = void;
51 };
52
53 template <typename T, typename = void> struct has_is_transparent : std::false_type
54 {
55 };
56
57 template <typename T>
58 struct has_is_transparent<T, typename make_void<typename T::is_transparent>::type>
59 : std::true_type
60 {
61 };
62
63 template <typename U> struct is_power_of_two_policy : std::false_type
64 {
65 };
66
67 template <std::size_t GrowthFactor>
68 struct is_power_of_two_policy<tsl::rh::power_of_two_growth_policy<GrowthFactor>> : std::true_type
69 {
70 };
71
72 // Only available in C++17, we need to be compatible with C++11
73 template <class T> const T &clamp(const T &v, const T &lo, const T &hi)
74 {
75 return std::min(hi, std::max(lo, v));
76 }
77
78 template <typename T, typename U>
79 static T numeric_cast(U value, const char *error_message = "numeric_cast() failed.")
80 {
81 T ret = static_cast<T>(value);
82 if (static_cast<U>(ret) != value) {
83 TSL_RH_THROW_OR_TERMINATE(std::runtime_error, error_message);
84 }
85
86 const bool is_same_signedness = (std::is_unsigned<T>::value && std::is_unsigned<U>::value) ||
87 (std::is_signed<T>::value && std::is_signed<U>::value);
88 if (!is_same_signedness && (ret < T{}) != (value < U{})) {
89 TSL_RH_THROW_OR_TERMINATE(std::runtime_error, error_message);
90 }
91
92 return ret;
93 }
94
95 template <class T, class Deserializer> static T deserialize_value(Deserializer &deserializer)
96 {
97 // MSVC < 2017 is not conformant, circumvent the problem by removing the
98 // template keyword
99#if defined(_MSC_VER) && _MSC_VER < 1910
100 return deserializer.Deserializer::operator()<T>();
101#else
102 return deserializer.Deserializer::template operator()<T>();
103#endif
104 }
105
106 /**
107 * Fixed size type used to represent size_type values on serialization. Need to
108 * be big enough to represent a std::size_t on 32 and 64 bits platforms, and
109 * must be the same size on both platforms.
110 */
111 using slz_size_type = std::uint64_t;
112 static_assert(std::numeric_limits<slz_size_type>::max() >=
113 std::numeric_limits<std::size_t>::max(),
114 "slz_size_type must be >= std::size_t");
115
116 using truncated_hash_type = std::uint32_t;
117
118 /**
119 * Helper class that stores a truncated hash if StoreHash is true and nothing
120 * otherwise.
121 */
122 template <bool StoreHash> class bucket_entry_hash
123 {
124 public:
125 bool bucket_hash_equal(std::size_t /*hash*/) const noexcept { return true; }
126
127 truncated_hash_type truncated_hash() const noexcept { return 0; }
128
129 protected:
130 void set_hash(truncated_hash_type /*hash*/) noexcept {}
131 };
132
133 template <> class bucket_entry_hash<true>
134 {
135 public:
136 bool bucket_hash_equal(std::size_t my_hash) const noexcept
137 {
138 return m_hash == truncated_hash_type(my_hash);
139 }
140
141 truncated_hash_type truncated_hash() const noexcept { return m_hash; }
142
143 protected:
144 void set_hash(truncated_hash_type my_hash) noexcept { m_hash = my_hash; }
145
146 private:
148 };
149
150 /**
151 * Each bucket entry has:
152 * - A value of type `ValueType`.
153 * - An integer to store how far the value of the bucket, if any, is from its
154 * ideal bucket (ex: if the current bucket 5 has the value 'foo' and
155 * `hash('foo') % nb_buckets` == 3, `dist_from_ideal_bucket()` will return 2 as
156 * the current value of the bucket is two buckets away from its ideal bucket) If
157 * there is no value in the bucket (i.e. `empty()` is true)
158 * `dist_from_ideal_bucket()` will be < 0.
159 * - A marker which tells us if the bucket is the last bucket of the bucket
160 * array (useful for the iterator of the hash table).
161 * - If `StoreHash` is true, 32 bits of the hash of the value, if any, are also
162 * stored in the bucket. If the size of the hash is more than 32 bits, it is
163 * truncated. We don't store the full hash as storing the hash is a potential
164 * opportunity to use the unused space due to the alignment of the bucket_entry
165 * structure. We can thus potentially store the hash without any extra space
166 * (which would not be possible with 64 bits of the hash).
167 */
168 template <typename ValueType, bool StoreHash>
169 class bucket_entry : public bucket_entry_hash<StoreHash>
170 {
172
173 public:
174 using value_type = ValueType;
175 using distance_type = std::int16_t;
176
183
190
191 bucket_entry(const bucket_entry &other) noexcept(
192 std::is_nothrow_copy_constructible<value_type>::value)
195 {
196 if (!other.empty()) {
197 ::new (static_cast<void *>(std::addressof(m_value))) value_type(other.value());
198 m_dist_from_ideal_bucket = other.m_dist_from_ideal_bucket;
199 }
200 tsl_rh_assert(empty() == other.empty());
201 }
202
203 /**
204 * Never really used, but still necessary as we must call resize on an empty
205 * `std::vector<bucket_entry>`. and we need to support move-only types. See
206 * robin_hash constructor for details.
207 */
208 bucket_entry(bucket_entry &&other) noexcept(
209 std::is_nothrow_move_constructible<value_type>::value)
210 : bucket_hash(std::move(other)),
213 {
214 if (!other.empty()) {
215 ::new (static_cast<void *>(std::addressof(m_value))) value_type(std::move(other.value()));
216 m_dist_from_ideal_bucket = other.m_dist_from_ideal_bucket;
217 }
218 tsl_rh_assert(empty() == other.empty());
219 }
220
221 bucket_entry &operator=(const bucket_entry &other) noexcept(
222 std::is_nothrow_copy_constructible<value_type>::value)
223 {
224 if (this != &other) {
225 clear();
226
227 bucket_hash::operator=(other);
228 if (!other.empty()) {
229 ::new (static_cast<void *>(std::addressof(m_value))) value_type(other.value());
230 }
231
232 m_dist_from_ideal_bucket = other.m_dist_from_ideal_bucket;
233 m_last_bucket = other.m_last_bucket;
234 }
235
236 return *this;
237 }
238
240
241 ~bucket_entry() noexcept { clear(); }
242
243 void clear() noexcept
244 {
245 if (!empty()) {
248 }
249 }
250
251 bool empty() const noexcept
252 {
254 }
255
256 value_type &value() noexcept
257 {
259#if defined(__cplusplus) && __cplusplus >= 201703L
260 return *std::launder(reinterpret_cast<value_type *>(std::addressof(m_value)));
261#else
262 return *reinterpret_cast<value_type *>(std::addressof(m_value));
263#endif
264 }
265
266 const value_type &value() const noexcept
267 {
269#if defined(__cplusplus) && __cplusplus >= 201703L
270 return *std::launder(reinterpret_cast<const value_type *>(std::addressof(m_value)));
271#else
272 return *reinterpret_cast<const value_type *>(std::addressof(m_value));
273#endif
274 }
275
277
278 bool last_bucket() const noexcept { return m_last_bucket; }
279
280 void set_as_last_bucket() noexcept { m_last_bucket = true; }
281
282 template <typename... Args>
284 truncated_hash_type my_hash, Args &&...value_type_args)
285 {
288
289 ::new (static_cast<void *>(std::addressof(m_value)))
290 value_type(std::forward<Args>(value_type_args)...);
291 this->set_hash(my_hash);
293
295 }
296
299 {
302
303 using std::swap;
304 swap(value, this->value());
306
307 if (StoreHash) {
308 const truncated_hash_type tmp_hash = this->truncated_hash();
309 this->set_hash(my_hash);
310 my_hash = tmp_hash;
311 }
312 else {
313 // Avoid warning of unused variable if StoreHash is false
314 TSL_RH_UNUSED(my_hash);
315 }
316 }
317
318 static truncated_hash_type truncate_hash(std::size_t my_hash) noexcept
319 {
320 return truncated_hash_type(my_hash);
321 }
322
323 private:
324 void destroy_value() noexcept
325 {
327 value().~value_type();
328 }
329
330 public:
333 static_assert(DIST_FROM_IDEAL_BUCKET_LIMIT <= std::numeric_limits<distance_type>::max() - 1,
334 "DIST_FROM_IDEAL_BUCKET_LIMIT must be <= "
335 "std::numeric_limits<distance_type>::max() - 1.");
336
337 private:
340 alignas(value_type) unsigned char m_value[sizeof(value_type)];
341 };
342
343 /**
344 * Internal common class used by `robin_map` and `robin_set`.
345 *
346 * ValueType is what will be stored by `robin_hash` (usually `std::pair<Key, T>`
347 * for map and `Key` for set).
348 *
349 * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in
350 * parameter and returns a reference to the key.
351 *
352 * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in
353 * parameter and returns a reference to the value. `ValueSelect` should be void
354 * if there is no value (in a set for example).
355 *
356 * The strong exception guarantee only holds if the expression
357 * `std::is_nothrow_swappable<ValueType>\:\:value &&
358 * std::is_nothrow_move_constructible<ValueType>\:\:value` is true.
359 *
360 * Behaviour is undefined if the destructor of `ValueType` throws.
361 */
362 template <class ValueType, class KeySelect, class ValueSelect, class Hash, class KeyEqual,
363 class Allocator, bool StoreHash, class GrowthPolicy>
364 class robin_hash : private Hash, private KeyEqual, private GrowthPolicy
365 {
366 private:
367 template <typename U>
368 using has_mapped_type = typename std::integral_constant<bool, !std::is_same<U, void>::value>;
369
370 static_assert(noexcept(std::declval<GrowthPolicy>().bucket_for_hash(std::size_t(0))),
371 "GrowthPolicy::bucket_for_hash must be noexcept.");
372 static_assert(noexcept(std::declval<GrowthPolicy>().clear()),
373 "GrowthPolicy::clear must be noexcept.");
374
375 public:
376 template <bool IsConst> class robin_iterator;
377
378 using key_type = typename KeySelect::key_type;
379 using value_type = ValueType;
380 using size_type = std::size_t;
381 using difference_type = std::ptrdiff_t;
382 using hasher = Hash;
383 using key_equal = KeyEqual;
384 using allocator_type = Allocator;
388 using const_pointer = const value_type *;
391
392 private:
393 /**
394 * Either store the hash because we are asked by the `StoreHash` template
395 * parameter or store the hash because it doesn't cost us anything in size and
396 * can be used to speed up rehash.
397 */
398 static constexpr bool STORE_HASH =
399 StoreHash ||
402 (sizeof(std::size_t) == sizeof(truncated_hash_type) ||
404 // Don't store the hash for primitive types with default hash.
405 (!std::is_arithmetic<key_type>::value || !std::is_same<Hash, std::hash<key_type>>::value));
406
407 /**
408 * Only use the stored hash on lookup if we are explicitly asked. We are not
409 * sure how slow the KeyEqual operation is. An extra comparison may slow
410 * things down with a fast KeyEqual.
411 */
412 static constexpr bool USE_STORED_HASH_ON_LOOKUP = StoreHash;
413
414 /**
415 * We can only use the hash on rehash if the size of the hash type is the same
416 * as the stored one or if we use a power of two modulo. In the case of the
417 * power of two modulo, we just mask the least significant bytes, we just have
418 * to check that the truncated_hash_type didn't truncated more bytes.
419 */
421 {
422 if (STORE_HASH && sizeof(std::size_t) == sizeof(truncated_hash_type)) {
424 return true;
425 }
427 return bucket_count == 0 ||
428 (bucket_count - 1) <= std::numeric_limits<truncated_hash_type>::max();
429 }
430 else {
432 return false;
433 }
434 }
435
438
440 typename std::allocator_traits<allocator_type>::template rebind_alloc<bucket_entry>;
441 using buckets_container_type = std::vector<bucket_entry, buckets_allocator>;
442
443 public:
444 /**
445 * The 'operator*()' and 'operator->()' methods return a const reference and
446 * const pointer respectively to the stored value type.
447 *
448 * In case of a map, to get a mutable reference to the value associated to a
449 * key (the '.second' in the stored pair), you have to call 'value()'.
450 *
451 * The main reason for this is that if we returned a `std::pair<Key, T>&`
452 * instead of a `const std::pair<Key, T>&`, the user may modify the key which
453 * will put the map in a undefined state.
454 */
455 template <bool IsConst> class robin_iterator
456 {
457 friend class robin_hash;
458
459 private:
461 typename std::conditional<IsConst, const bucket_entry *, bucket_entry *>::type;
462
463 robin_iterator(bucket_entry_ptr bucket) noexcept : m_bucket(bucket) {}
464
465 public:
466 using iterator_category = std::forward_iterator_tag;
467 using value_type = const typename robin_hash::value_type;
468 using difference_type = std::ptrdiff_t;
471
472 robin_iterator() noexcept = default;
473
474 // Copy constructor from iterator to const_iterator.
475 template <bool TIsConst = IsConst, typename std::enable_if<TIsConst>::type * = nullptr>
476 robin_iterator(const robin_iterator<!TIsConst> &other) noexcept : m_bucket(other.m_bucket)
477 {
478 }
479
480 robin_iterator(const robin_iterator &other) = default;
481 robin_iterator(robin_iterator &&other) = default;
482 robin_iterator &operator=(const robin_iterator &other) = default;
484
485 const typename robin_hash::key_type &key() const { return KeySelect()(m_bucket->value()); }
486
487 template <class U = ValueSelect,
488 typename std::enable_if<has_mapped_type<U>::value && IsConst>::type * = nullptr>
489 const typename U::value_type &value() const
490 {
491 return U()(m_bucket->value());
492 }
493
494 template <class U = ValueSelect,
495 typename std::enable_if<has_mapped_type<U>::value && !IsConst>::type * = nullptr>
496 typename U::value_type &value() const
497 {
498 return U()(m_bucket->value());
499 }
500
501 reference operator*() const { return m_bucket->value(); }
502
503 pointer operator->() const { return std::addressof(m_bucket->value()); }
504
506 {
507 while (true) {
508 if (m_bucket->last_bucket()) {
509 ++m_bucket;
510 return *this;
511 }
512
513 ++m_bucket;
514 if (!m_bucket->empty()) {
515 return *this;
516 }
517 }
518 }
519
521 {
522 robin_iterator tmp(*this);
523 ++*this;
524
525 return tmp;
526 }
527
528 friend bool operator==(const robin_iterator &lhs, const robin_iterator &rhs)
529 {
530 return lhs.m_bucket == rhs.m_bucket;
531 }
532
533 friend bool operator!=(const robin_iterator &lhs, const robin_iterator &rhs)
534 {
535 return !(lhs == rhs);
536 }
537
538 private:
540 };
541
542 public:
543#if defined(__cplusplus) && __cplusplus >= 201402L
544 robin_hash(size_type bucket_count, const Hash &my_hash, const KeyEqual &equal,
545 const Allocator &alloc, float min_load_factor = DEFAULT_MIN_LOAD_FACTOR,
547 : Hash(my_hash), KeyEqual(equal), GrowthPolicy(bucket_count),
551 {
553 TSL_RH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maximum bucket count.");
554 }
555
556 if (m_bucket_count > 0) {
558 m_buckets_data.back().set_as_last_bucket();
559 }
560
563 }
564#else
565 /**
566 * C++11 doesn't support the creation of a std::vector with a custom allocator
567 * and 'count' default-inserted elements. The needed constructor `explicit
568 * vector(size_type count, const Allocator& alloc = Allocator());` is only
569 * available in C++14 and later. We thus must resize after using the
570 * `vector(const Allocator& alloc)` constructor.
571 *
572 * We can't use `vector(size_type count, const T& value, const Allocator&
573 * alloc)` as it requires the value T to be copyable.
574 */
575 robin_hash(size_type bucket_count, const Hash &my_hash, const KeyEqual &equal,
576 const Allocator &alloc, float min_load_factor = DEFAULT_MIN_LOAD_FACTOR,
578 : Hash(my_hash), KeyEqual(equal), GrowthPolicy(bucket_count), m_buckets_data(alloc),
580 {
582 TSL_RH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maximum bucket count.");
583 }
584
585 if (m_bucket_count > 0) {
587 m_buckets = m_buckets_data.data();
588
590 m_buckets_data.back().set_as_last_bucket();
591 }
592
595 }
596#endif
597
608
609 robin_hash(robin_hash &&other) noexcept(
610 std::is_nothrow_move_constructible<Hash>::value &&
611 std::is_nothrow_move_constructible<KeyEqual>::value &&
612 std::is_nothrow_move_constructible<GrowthPolicy>::value &&
613 std::is_nothrow_move_constructible<buckets_container_type>::value)
614 : Hash(std::move(static_cast<Hash &>(other))),
615 KeyEqual(std::move(static_cast<KeyEqual &>(other))),
616 GrowthPolicy(std::move(static_cast<GrowthPolicy &>(other))),
617 m_buckets_data(std::move(other.m_buckets_data)),
624 {
625 other.clear_and_shrink();
626 }
627
629 {
630 if (&other != this) {
631 Hash::operator=(other);
632 KeyEqual::operator=(other);
633 GrowthPolicy::operator=(other);
634
639
643
646 }
647
648 return *this;
649 }
650
652 {
653 other.swap(*this);
654 other.clear_and_shrink();
655
656 return *this;
657 }
658
659 allocator_type get_allocator() const { return m_buckets_data.get_allocator(); }
660
661 /*
662 * Iterators
663 */
664 iterator begin() noexcept
665 {
666 std::size_t i = 0;
667 while (i < m_bucket_count && m_buckets[i].empty()) {
668 i++;
669 }
670
671 return iterator(m_buckets + i);
672 }
673
674 const_iterator begin() const noexcept { return cbegin(); }
675
676 const_iterator cbegin() const noexcept
677 {
678 std::size_t i = 0;
679 while (i < m_bucket_count && m_buckets[i].empty()) {
680 i++;
681 }
682
683 return const_iterator(m_buckets + i);
684 }
685
686 iterator end() noexcept { return iterator(m_buckets + m_bucket_count); }
687
688 const_iterator end() const noexcept { return cend(); }
689
691
692 /*
693 * Capacity
694 */
695 bool empty() const noexcept { return m_nb_elements == 0; }
696
697 size_type size() const noexcept { return m_nb_elements; }
698
699 size_type max_size() const noexcept { return m_buckets_data.max_size(); }
700
701 /*
702 * Modifiers
703 */
704 void clear() noexcept
705 {
706 if (m_min_load_factor > 0.0f) {
708 }
709 else {
710 for (auto &bucket : m_buckets_data) {
711 bucket.clear();
712 }
713
714 m_nb_elements = 0;
715 m_grow_on_next_insert = false;
716 }
717 }
718
719 template <typename P> std::pair<iterator, bool> insert(P &&value)
720 {
721 return insert_impl(KeySelect()(value), std::forward<P>(value));
722 }
723
724 template <typename P> iterator insert_hint(const_iterator hint, P &&value)
725 {
726 if (hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) {
727 return mutable_iterator(hint);
728 }
729
730 return insert(std::forward<P>(value)).first;
731 }
732
733 template <class InputIt> void insert(InputIt first, InputIt last)
734 {
735 if (std::is_base_of<std::forward_iterator_tag,
736 typename std::iterator_traits<InputIt>::iterator_category>::value) {
737 const auto nb_elements_insert = std::distance(first, last);
738 const size_type nb_free_buckets = m_load_threshold - size();
740
741 if (nb_elements_insert > 0 && nb_free_buckets < size_type(nb_elements_insert)) {
742 reserve(size() + size_type(nb_elements_insert));
743 }
744 }
745
746 for (; first != last; ++first) {
747 insert(*first);
748 }
749 }
750
751 template <class K, class M> std::pair<iterator, bool> insert_or_assign(K &&key, M &&obj)
752 {
753 auto it = try_emplace(std::forward<K>(key), std::forward<M>(obj));
754 if (!it.second) {
755 it.first.value() = std::forward<M>(obj);
756 }
757
758 return it;
759 }
760
761 template <class K, class M> iterator insert_or_assign(const_iterator hint, K &&key, M &&obj)
762 {
763 if (hint != cend() && compare_keys(KeySelect()(*hint), key)) {
764 auto it = mutable_iterator(hint);
765 it.value() = std::forward<M>(obj);
766
767 return it;
768 }
769
770 return insert_or_assign(std::forward<K>(key), std::forward<M>(obj)).first;
771 }
772
773 template <class... Args> std::pair<iterator, bool> emplace(Args &&...args)
774 {
775 return insert(value_type(std::forward<Args>(args)...));
776 }
777
778 template <class... Args> iterator emplace_hint(const_iterator hint, Args &&...args)
779 {
780 return insert_hint(hint, value_type(std::forward<Args>(args)...));
781 }
782
783 template <class K, class... Args> std::pair<iterator, bool> try_emplace(K &&key, Args &&...args)
784 {
785 return insert_impl(key, std::piecewise_construct, std::forward_as_tuple(std::forward<K>(key)),
786 std::forward_as_tuple(std::forward<Args>(args)...));
787 }
788
789 template <class K, class... Args>
790 iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args)
791 {
792 if (hint != cend() && compare_keys(KeySelect()(*hint), key)) {
793 return mutable_iterator(hint);
794 }
795
796 return try_emplace(std::forward<K>(key), std::forward<Args>(args)...).first;
797 }
798
799 /**
800 * Here to avoid `template<class K> size_type erase(const K& key)` being used
801 * when we use an `iterator` instead of a `const_iterator`.
802 */
804 {
806
807 /**
808 * Erase bucket used a backward shift after clearing the bucket.
809 * Check if there is a new value in the bucket, if not get the next
810 * non-empty.
811 */
812 if (pos.m_bucket->empty()) {
813 ++pos;
814 }
815
817
818 return pos;
819 }
820
822
824 {
825 if (first == last) {
826 return mutable_iterator(first);
827 }
828
829 auto first_mutable = mutable_iterator(first);
830 auto last_mutable = mutable_iterator(last);
831 for (auto it = first_mutable.m_bucket; it != last_mutable.m_bucket; ++it) {
832 if (!it->empty()) {
833 it->clear();
835 }
836 }
837
838 if (last_mutable == end()) {
840 return end();
841 }
842
843 /*
844 * Backward shift on the values which come after the deleted values.
845 * We try to move the values closer to their ideal bucket.
846 */
847 auto icloser_bucket = static_cast<std::size_t>(first_mutable.m_bucket - m_buckets);
848 auto ito_move_closer_value = static_cast<std::size_t>(last_mutable.m_bucket - m_buckets);
849 tsl_rh_assert(ito_move_closer_value > icloser_bucket);
850
851 const std::size_t ireturn_bucket =
852 ito_move_closer_value -
853 std::min(ito_move_closer_value - icloser_bucket,
854 std::size_t(m_buckets[ito_move_closer_value].dist_from_ideal_bucket()));
855
856 while (ito_move_closer_value < m_bucket_count &&
857 m_buckets[ito_move_closer_value].dist_from_ideal_bucket() > 0) {
858 icloser_bucket =
859 ito_move_closer_value -
860 std::min(ito_move_closer_value - icloser_bucket,
861 std::size_t(m_buckets[ito_move_closer_value].dist_from_ideal_bucket()));
862
863 tsl_rh_assert(m_buckets[icloser_bucket].empty());
864 const distance_type new_distance =
865 distance_type(m_buckets[ito_move_closer_value].dist_from_ideal_bucket() -
866 (ito_move_closer_value - icloser_bucket));
867 m_buckets[icloser_bucket].set_value_of_empty_bucket(
868 new_distance, m_buckets[ito_move_closer_value].truncated_hash(),
869 std::move(m_buckets[ito_move_closer_value].value()));
870 m_buckets[ito_move_closer_value].clear();
871
872 ++icloser_bucket;
873 ++ito_move_closer_value;
874 }
875
877
878 return iterator(m_buckets + ireturn_bucket);
879 }
880
881 template <class K> size_type erase(const K &key) { return erase(key, hash_key(key)); }
882
883 template <class K> size_type erase(const K &key, std::size_t my_hash)
884 {
885 auto it = find(key, my_hash);
886 if (it != end()) {
889
890 return 1;
891 }
892 else {
893 return 0;
894 }
895 }
896
897 void swap(robin_hash &other)
898 {
899 using std::swap;
900
901 swap(static_cast<Hash &>(*this), static_cast<Hash &>(other));
902 swap(static_cast<KeyEqual &>(*this), static_cast<KeyEqual &>(other));
903 swap(static_cast<GrowthPolicy &>(*this), static_cast<GrowthPolicy &>(other));
905 swap(m_buckets, other.m_buckets);
913 }
914
915 /*
916 * Lookup
917 */
918 template <class K, class U = ValueSelect,
919 typename std::enable_if<has_mapped_type<U>::value>::type * = nullptr>
920 typename U::value_type &at(const K &key)
921 {
922 return at(key, hash_key(key));
923 }
924
925 template <class K, class U = ValueSelect,
926 typename std::enable_if<has_mapped_type<U>::value>::type * = nullptr>
927 typename U::value_type &at(const K &key, std::size_t my_hash)
928 {
929 return const_cast<typename U::value_type &>(
930 static_cast<const robin_hash *>(this)->at(key, my_hash));
931 }
932
933 template <class K, class U = ValueSelect,
934 typename std::enable_if<has_mapped_type<U>::value>::type * = nullptr>
935 const typename U::value_type &at(const K &key) const
936 {
937 return at(key, hash_key(key));
938 }
939
940 template <class K, class U = ValueSelect,
941 typename std::enable_if<has_mapped_type<U>::value>::type * = nullptr>
942 const typename U::value_type &at(const K &key, std::size_t my_hash) const
943 {
944 auto it = find(key, my_hash);
945 if (it != cend()) {
946 return it.value();
947 }
948 else {
949 TSL_RH_THROW_OR_TERMINATE(std::out_of_range, "Couldn't find key.");
950 }
951 }
952
953 template <class K, class U = ValueSelect,
954 typename std::enable_if<has_mapped_type<U>::value>::type * = nullptr>
955 typename U::value_type &operator[](K &&key)
956 {
957 return try_emplace(std::forward<K>(key)).first.value();
958 }
959
960 template <class K> size_type count(const K &key) const { return count(key, hash_key(key)); }
961
962 template <class K> size_type count(const K &key, std::size_t my_hash) const
963 {
964 if (find(key, my_hash) != cend()) {
965 return 1;
966 }
967 else {
968 return 0;
969 }
970 }
971
972 template <class K> iterator find(const K &key) { return find_impl(key, hash_key(key)); }
973
974 template <class K> iterator find(const K &key, std::size_t my_hash)
975 {
976 return find_impl(key, my_hash);
977 }
978
979 template <class K> const_iterator find(const K &key) const
980 {
981 return find_impl(key, hash_key(key));
982 }
983
984 template <class K> const_iterator find(const K &key, std::size_t my_hash) const
985 {
986 return find_impl(key, my_hash);
987 }
988
989 template <class K> bool contains(const K &key) const { return contains(key, hash_key(key)); }
990
991 template <class K> bool contains(const K &key, std::size_t my_hash) const
992 {
993 return count(key, my_hash) != 0;
994 }
995
996 template <class K> std::pair<iterator, iterator> equal_range(const K &key)
997 {
998 return equal_range(key, hash_key(key));
999 }
1000
1001 template <class K> std::pair<iterator, iterator> equal_range(const K &key, std::size_t my_hash)
1002 {
1003 iterator it = find(key, my_hash);
1004 return std::make_pair(it, (it == end()) ? it : std::next(it));
1005 }
1006
1007 template <class K> std::pair<const_iterator, const_iterator> equal_range(const K &key) const
1008 {
1009 return equal_range(key, hash_key(key));
1010 }
1011
1012 template <class K>
1013 std::pair<const_iterator, const_iterator> equal_range(const K &key, std::size_t my_hash) const
1014 {
1015 const_iterator it = find(key, my_hash);
1016 return std::make_pair(it, (it == cend()) ? it : std::next(it));
1017 }
1018
1019 /*
1020 * Bucket interface
1021 */
1023
1025 {
1026 return std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size());
1027 }
1028
1029 /*
1030 * Hash policy
1031 */
1032 float load_factor() const
1033 {
1034 if (bucket_count() == 0) {
1035 return 0;
1036 }
1037
1038 return float(m_nb_elements) / float(bucket_count());
1039 }
1040
1041 float min_load_factor() const { return m_min_load_factor; }
1042
1043 float max_load_factor() const { return m_max_load_factor; }
1044
1045 void min_load_factor(float ml)
1046 {
1048 }
1049
1056
1057 void rehash(size_type my_count)
1058 {
1059 my_count = std::max(my_count, size_type(std::ceil(float(size()) / max_load_factor())));
1060 rehash_impl(my_count);
1061 }
1062
1063 void reserve(size_type my_count)
1064 {
1065 rehash(size_type(std::ceil(float(my_count) / max_load_factor())));
1066 }
1067
1068 /*
1069 * Observers
1070 */
1071 hasher hash_function() const { return static_cast<const Hash &>(*this); }
1072
1073 key_equal key_eq() const { return static_cast<const KeyEqual &>(*this); }
1074
1075 /*
1076 * Other
1077 */
1079 {
1080 return iterator(const_cast<bucket_entry *>(pos.m_bucket));
1081 }
1082
1083 template <class Serializer> void serialize(Serializer &serializer) const
1084 {
1085 serialize_impl(serializer);
1086 }
1087
1088 template <class Deserializer> void deserialize(Deserializer &deserializer, bool hash_compatible)
1089 {
1090 deserialize_impl(deserializer, hash_compatible);
1091 }
1092
1093 private:
1094 template <class K> std::size_t hash_key(const K &key) const { return Hash::operator()(key); }
1095
1096 template <class K1, class K2> bool compare_keys(const K1 &key1, const K2 &key2) const
1097 {
1098 return KeyEqual::operator()(key1, key2);
1099 }
1100
1101 std::size_t bucket_for_hash(std::size_t my_hash) const
1102 {
1103 const std::size_t bucket = GrowthPolicy::bucket_for_hash(my_hash);
1104 tsl_rh_assert(bucket < m_bucket_count || (bucket == 0 && m_bucket_count == 0));
1105
1106 return bucket;
1107 }
1108
1109 template <class U = GrowthPolicy,
1110 typename std::enable_if<is_power_of_two_policy<U>::value>::type * = nullptr>
1111 std::size_t next_bucket(std::size_t index) const noexcept
1112 {
1113 tsl_rh_assert(index < bucket_count());
1114
1115 return (index + 1) & this->m_mask;
1116 }
1117
1118 template <class U = GrowthPolicy,
1119 typename std::enable_if<!is_power_of_two_policy<U>::value>::type * = nullptr>
1120 std::size_t next_bucket(std::size_t index) const noexcept
1121 {
1122 tsl_rh_assert(index < bucket_count());
1123
1124 index++;
1125 return (index != bucket_count()) ? index : 0;
1126 }
1127
1128 template <class K> iterator find_impl(const K &key, std::size_t my_hash)
1129 {
1130 return mutable_iterator(static_cast<const robin_hash *>(this)->find(key, my_hash));
1131 }
1132
1133 template <class K> const_iterator find_impl(const K &key, std::size_t my_hash) const
1134 {
1135 std::size_t ibucket = bucket_for_hash(my_hash);
1136 distance_type dist_from_ideal_bucket = 0;
1137
1138 while (dist_from_ideal_bucket <= m_buckets[ibucket].dist_from_ideal_bucket()) {
1139 if (TSL_RH_LIKELY(
1140 (!USE_STORED_HASH_ON_LOOKUP || m_buckets[ibucket].bucket_hash_equal(my_hash)) &&
1141 compare_keys(KeySelect()(m_buckets[ibucket].value()), key))) {
1142 return const_iterator(m_buckets + ibucket);
1143 }
1144
1145 ibucket = next_bucket(ibucket);
1146 dist_from_ideal_bucket++;
1147 }
1148
1149 return cend();
1150 }
1151
1153 {
1154 pos.m_bucket->clear();
1155 m_nb_elements--;
1156
1157 /**
1158 * Backward shift, swap the empty bucket, previous_ibucket, with the values
1159 * on its right, ibucket, until we cross another empty bucket or if the
1160 * other bucket has a distance_from_ideal_bucket == 0.
1161 *
1162 * We try to move the values closer to their ideal bucket.
1163 */
1164 auto previous_ibucket = static_cast<std::size_t>(pos.m_bucket - m_buckets);
1165 auto ibucket = next_bucket(previous_ibucket);
1166
1167 while (m_buckets[ibucket].dist_from_ideal_bucket() > 0) {
1168 tsl_rh_assert(m_buckets[previous_ibucket].empty());
1169
1170 const auto new_distance = distance_type(m_buckets[ibucket].dist_from_ideal_bucket() - 1);
1171 m_buckets[previous_ibucket].set_value_of_empty_bucket(
1172 new_distance, m_buckets[ibucket].truncated_hash(),
1173 std::move(m_buckets[ibucket].value()));
1174 m_buckets[ibucket].clear();
1175
1176 previous_ibucket = ibucket;
1177 ibucket = next_bucket(ibucket);
1178 }
1179 }
1180
1181 template <class K, class... Args>
1182 std::pair<iterator, bool> insert_impl(const K &key, Args &&...value_type_args)
1183 {
1184 const std::size_t my_hash = hash_key(key);
1185
1186 std::size_t ibucket = bucket_for_hash(my_hash);
1187 distance_type dist_from_ideal_bucket = 0;
1188
1189 while (dist_from_ideal_bucket <= m_buckets[ibucket].dist_from_ideal_bucket()) {
1190 if ((!USE_STORED_HASH_ON_LOOKUP || m_buckets[ibucket].bucket_hash_equal(my_hash)) &&
1191 compare_keys(KeySelect()(m_buckets[ibucket].value()), key)) {
1192 return std::make_pair(iterator(m_buckets + ibucket), false);
1193 }
1194
1195 ibucket = next_bucket(ibucket);
1196 dist_from_ideal_bucket++;
1197 }
1198
1199 while (rehash_on_extreme_load(dist_from_ideal_bucket)) {
1200 ibucket = bucket_for_hash(my_hash);
1201 dist_from_ideal_bucket = 0;
1202
1203 while (dist_from_ideal_bucket <= m_buckets[ibucket].dist_from_ideal_bucket()) {
1204 ibucket = next_bucket(ibucket);
1205 dist_from_ideal_bucket++;
1206 }
1207 }
1208
1209 if (m_buckets[ibucket].empty()) {
1210 m_buckets[ibucket].set_value_of_empty_bucket(dist_from_ideal_bucket,
1212 std::forward<Args>(value_type_args)...);
1213 }
1214 else {
1215 insert_value(ibucket, dist_from_ideal_bucket, bucket_entry::truncate_hash(my_hash),
1216 std::forward<Args>(value_type_args)...);
1217 }
1218
1219 m_nb_elements++;
1220 /*
1221 * The value will be inserted in ibucket in any case, either because it was
1222 * empty or by stealing the bucket (robin hood).
1223 */
1224 return std::make_pair(iterator(m_buckets + ibucket), true);
1225 }
1226
1227 template <class... Args>
1228 void insert_value(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1229 truncated_hash_type my_hash, Args &&...value_type_args)
1230 {
1231 value_type value(std::forward<Args>(value_type_args)...);
1232 insert_value_impl(ibucket, dist_from_ideal_bucket, my_hash, value);
1233 }
1234
1235 void insert_value(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1236 truncated_hash_type my_hash, value_type &&value)
1237 {
1238 insert_value_impl(ibucket, dist_from_ideal_bucket, my_hash, value);
1239 }
1240
1241 /*
1242 * We don't use `value_type&& value` as last argument due to a bug in MSVC
1243 * when `value_type` is a pointer, The compiler is not able to see the
1244 * difference between `std::string*` and `std::string*&&` resulting in a
1245 * compilation error.
1246 *
1247 * The `value` will be in a moved state at the end of the function.
1248 */
1249 void insert_value_impl(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1250 truncated_hash_type my_hash, value_type &value)
1251 {
1252 tsl_rh_assert(dist_from_ideal_bucket > m_buckets[ibucket].dist_from_ideal_bucket());
1253 m_buckets[ibucket].swap_with_value_in_bucket(dist_from_ideal_bucket, my_hash, value);
1254 ibucket = next_bucket(ibucket);
1255 dist_from_ideal_bucket++;
1256
1257 while (!m_buckets[ibucket].empty()) {
1258 if (dist_from_ideal_bucket > m_buckets[ibucket].dist_from_ideal_bucket()) {
1259 if (dist_from_ideal_bucket > bucket_entry::DIST_FROM_IDEAL_BUCKET_LIMIT) {
1260 /**
1261 * The number of probes is really high, rehash the map on the next
1262 * insert. Difficult to do now as rehash may throw an exception.
1263 */
1264 m_grow_on_next_insert = true;
1265 }
1266
1267 m_buckets[ibucket].swap_with_value_in_bucket(dist_from_ideal_bucket, my_hash, value);
1268 }
1269
1270 ibucket = next_bucket(ibucket);
1271 dist_from_ideal_bucket++;
1272 }
1273
1274 m_buckets[ibucket].set_value_of_empty_bucket(dist_from_ideal_bucket, my_hash,
1275 std::move(value));
1276 }
1277
1278 void rehash_impl(size_type my_count)
1279 {
1280 robin_hash new_table(my_count, static_cast<Hash &>(*this), static_cast<KeyEqual &>(*this),
1282
1283 const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_table.bucket_count());
1284 for (auto &bucket : m_buckets_data) {
1285 if (bucket.empty()) {
1286 continue;
1287 }
1288
1289 const std::size_t my_hash = use_stored_hash
1290 ? bucket.truncated_hash()
1291 : new_table.hash_key(KeySelect()(bucket.value()));
1292
1293 new_table.insert_value_on_rehash(new_table.bucket_for_hash(my_hash), 0,
1295 std::move(bucket.value()));
1296 }
1297
1298 new_table.m_nb_elements = m_nb_elements;
1299 new_table.swap(*this);
1300 }
1301
1302 void clear_and_shrink() noexcept
1303 {
1304 GrowthPolicy::clear();
1305 m_buckets_data.clear();
1307 m_bucket_count = 0;
1308 m_nb_elements = 0;
1309 m_load_threshold = 0;
1310 m_grow_on_next_insert = false;
1312 }
1313
1314 void insert_value_on_rehash(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1315 truncated_hash_type my_hash, value_type &&value)
1316 {
1317 while (true) {
1318 if (dist_from_ideal_bucket > m_buckets[ibucket].dist_from_ideal_bucket()) {
1319 if (m_buckets[ibucket].empty()) {
1320 m_buckets[ibucket].set_value_of_empty_bucket(dist_from_ideal_bucket, my_hash,
1321 std::move(value));
1322 return;
1323 }
1324 else {
1325 m_buckets[ibucket].swap_with_value_in_bucket(dist_from_ideal_bucket, my_hash, value);
1326 }
1327 }
1328
1329 dist_from_ideal_bucket++;
1330 ibucket = next_bucket(ibucket);
1331 }
1332 }
1333
1334 /**
1335 * Grow the table if m_grow_on_next_insert is true or we reached the
1336 * max_load_factor. Shrink the table if m_try_shrink_on_next_insert is true
1337 * (an erase occurred) and we're below the min_load_factor.
1338 *
1339 * Return true if the table has been rehashed.
1340 */
1341 bool rehash_on_extreme_load(distance_type curr_dist_from_ideal_bucket)
1342 {
1344 curr_dist_from_ideal_bucket > bucket_entry::DIST_FROM_IDEAL_BUCKET_LIMIT ||
1345 size() >= m_load_threshold) {
1346 rehash_impl(GrowthPolicy::next_bucket_count());
1347 m_grow_on_next_insert = false;
1348
1349 return true;
1350 }
1351
1354 if (m_min_load_factor != 0.0f && load_factor() < m_min_load_factor) {
1355 reserve(size() + 1);
1356
1357 return true;
1358 }
1359 }
1360
1361 return false;
1362 }
1363
1364 template <class Serializer> void serialize_impl(Serializer &serializer) const
1365 {
1367 serializer(version);
1368
1369 // Indicate if the truncated hash of each bucket is stored. Use a
1370 // std::int16_t instead of a bool to avoid the need for the serializer to
1371 // support an extra 'bool' type.
1372 const auto hash_stored_for_bucket = static_cast<std::int16_t>(STORE_HASH);
1373 serializer(hash_stored_for_bucket);
1374
1375 const slz_size_type nb_elements = m_nb_elements;
1376 serializer(nb_elements);
1377
1379 serializer(bucket_count);
1380
1381 const float min_load_factor = m_min_load_factor;
1382 serializer(min_load_factor);
1383
1384 const float max_load_factor = m_max_load_factor;
1385 serializer(max_load_factor);
1386
1387 for (const bucket_entry &bucket : m_buckets_data) {
1388 if (bucket.empty()) {
1389 const std::int16_t empty_bucket = bucket_entry::EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET;
1390 serializer(empty_bucket);
1391 }
1392 else {
1393 const std::int16_t dist_from_ideal_bucket = bucket.dist_from_ideal_bucket();
1394 serializer(dist_from_ideal_bucket);
1395 if (STORE_HASH) {
1396 const std::uint32_t truncated_hash = bucket.truncated_hash();
1397 serializer(truncated_hash);
1398 }
1399 serializer(bucket.value());
1400 }
1401 }
1402 }
1403
1404 template <class Deserializer>
1405 void deserialize_impl(Deserializer &deserializer, bool hash_compatible)
1406 {
1407 tsl_rh_assert(m_buckets_data.empty()); // Current hash table must be empty
1408
1409 const slz_size_type version = deserialize_value<slz_size_type>(deserializer);
1410 // For now we only have one version of the serialization protocol.
1411 // If it doesn't match there is a problem with the file.
1412 if (version != SERIALIZATION_PROTOCOL_VERSION) {
1413 TSL_RH_THROW_OR_TERMINATE(std::runtime_error, "Can't deserialize the ordered_map/set. "
1414 "The protocol version header is invalid.");
1415 }
1416
1417 const bool hash_stored_for_bucket =
1418 deserialize_value<std::int16_t>(deserializer) ? true : false;
1419 if (hash_compatible && STORE_HASH != hash_stored_for_bucket) {
1420 TSL_RH_THROW_OR_TERMINATE(std::runtime_error,
1421 "Can't deserialize a map with a different StoreHash "
1422 "than the one used during the serialization when "
1423 "hash compatibility is used");
1424 }
1425
1426 const slz_size_type nb_elements = deserialize_value<slz_size_type>(deserializer);
1427 const slz_size_type bucket_count_ds = deserialize_value<slz_size_type>(deserializer);
1428 const float min_load_factor = deserialize_value<float>(deserializer);
1429 const float max_load_factor = deserialize_value<float>(deserializer);
1430
1431 if (min_load_factor < MINIMUM_MIN_LOAD_FACTOR || min_load_factor > MAXIMUM_MIN_LOAD_FACTOR) {
1432 TSL_RH_THROW_OR_TERMINATE(std::runtime_error,
1433 "Invalid min_load_factor. Check that the serializer "
1434 "and deserializer support floats correctly as they "
1435 "can be converted implicitly to ints.");
1436 }
1437
1438 if (max_load_factor < MINIMUM_MAX_LOAD_FACTOR || max_load_factor > MAXIMUM_MAX_LOAD_FACTOR) {
1439 TSL_RH_THROW_OR_TERMINATE(std::runtime_error,
1440 "Invalid max_load_factor. Check that the serializer "
1441 "and deserializer support floats correctly as they "
1442 "can be converted implicitly to ints.");
1443 }
1444
1445 this->min_load_factor(min_load_factor);
1446 this->max_load_factor(max_load_factor);
1447
1448 if (bucket_count_ds == 0) {
1449 tsl_rh_assert(nb_elements == 0);
1450 return;
1451 }
1452
1453 if (!hash_compatible) {
1454 reserve(numeric_cast<size_type>(nb_elements, "Deserialized nb_elements is too big."));
1455 for (slz_size_type ibucket = 0; ibucket < bucket_count_ds; ibucket++) {
1456 const distance_type dist_from_ideal_bucket =
1457 deserialize_value<std::int16_t>(deserializer);
1458 if (dist_from_ideal_bucket != bucket_entry::EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET) {
1459 if (hash_stored_for_bucket) {
1460 TSL_RH_UNUSED(deserialize_value<std::uint32_t>(deserializer));
1461 }
1462
1463 insert(deserialize_value<value_type>(deserializer));
1464 }
1465 }
1466
1467 tsl_rh_assert(nb_elements == size());
1468 }
1469 else {
1471 numeric_cast<size_type>(bucket_count_ds, "Deserialized bucket_count is too big.");
1472
1473 GrowthPolicy::operator=(GrowthPolicy(m_bucket_count));
1474 // GrowthPolicy should not modify the bucket count we got from
1475 // deserialization
1476 if (m_bucket_count != bucket_count_ds) {
1477 TSL_RH_THROW_OR_TERMINATE(std::runtime_error, "The GrowthPolicy is not the same even "
1478 "though hash_compatible is true.");
1479 }
1480
1482 numeric_cast<size_type>(nb_elements, "Deserialized nb_elements is too big.");
1484 m_buckets = m_buckets_data.data();
1485
1486 for (bucket_entry &bucket : m_buckets_data) {
1487 const distance_type dist_from_ideal_bucket =
1488 deserialize_value<std::int16_t>(deserializer);
1489 if (dist_from_ideal_bucket != bucket_entry::EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET) {
1490 truncated_hash_type truncated_hash = 0;
1491 if (hash_stored_for_bucket) {
1492 tsl_rh_assert(hash_stored_for_bucket);
1493 truncated_hash = deserialize_value<std::uint32_t>(deserializer);
1494 }
1495
1496 bucket.set_value_of_empty_bucket(dist_from_ideal_bucket, truncated_hash,
1497 deserialize_value<value_type>(deserializer));
1498 }
1499 }
1500
1501 if (!m_buckets_data.empty()) {
1502 m_buckets_data.back().set_as_last_bucket();
1503 }
1504 }
1505 }
1506
1507 public:
1509
1510 static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f;
1511 static constexpr float MINIMUM_MAX_LOAD_FACTOR = 0.2f;
1512 static constexpr float MAXIMUM_MAX_LOAD_FACTOR = 0.95f;
1513
1514 static constexpr float DEFAULT_MIN_LOAD_FACTOR = 0.0f;
1515 static constexpr float MINIMUM_MIN_LOAD_FACTOR = 0.0f;
1516 static constexpr float MAXIMUM_MIN_LOAD_FACTOR = 0.15f;
1517
1519 "MINIMUM_MAX_LOAD_FACTOR should be < MAXIMUM_MAX_LOAD_FACTOR");
1521 "MINIMUM_MIN_LOAD_FACTOR should be < MAXIMUM_MIN_LOAD_FACTOR");
1523 "MAXIMUM_MIN_LOAD_FACTOR should be < MINIMUM_MAX_LOAD_FACTOR");
1524
1525 private:
1526 /**
1527 * Protocol version currently used for serialization.
1528 */
1530
1531 /**
1532 * Return an always valid pointer to an static empty bucket_entry with
1533 * last_bucket() == true.
1534 */
1536 {
1537 static bucket_entry empty_bucket(true);
1538 tsl_rh_assert(empty_bucket.empty());
1539 return &empty_bucket;
1540 }
1541
1542 private:
1544
1545 /**
1546 * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points
1547 * to static_empty_bucket_ptr. This variable is useful to avoid the cost of
1548 * checking if m_buckets_data is empty when trying to find an element.
1549 *
1550 * TODO Remove m_buckets_data and only use a pointer instead of a
1551 * pointer+vector to save some space in the robin_hash object. Manage the
1552 * Allocator manually.
1553 */
1555
1556 /**
1557 * Used a lot in find, avoid the call to m_buckets_data.size() which is a bit
1558 * slower.
1559 */
1561
1563
1565
1568
1570
1571 /**
1572 * We can't shrink down the map on erase operations as the erase methods need
1573 * to return the next iterator. Shrinking the map would invalidate all the
1574 * iterators and we could not return the next iterator in a meaningful way, On
1575 * erase, we thus just indicate on erase that we should try to shrink the hash
1576 * table on the next insert if we go below the min_load_factor.
1577 */
1579 };
1580
1581} // namespace tsl::detail_robin_hash
1582// NOLINTEND
1583
1584#endif
bool bucket_hash_equal(std::size_t my_hash) const noexcept
Definition robin_hash.h:136
void set_hash(truncated_hash_type my_hash) noexcept
Definition robin_hash.h:144
truncated_hash_type m_hash
Definition robin_hash.h:147
truncated_hash_type truncated_hash() const noexcept
Definition robin_hash.h:141
Definition robin_hash.h:123
truncated_hash_type truncated_hash() const noexcept
Definition robin_hash.h:127
bool bucket_hash_equal(std::size_t) const noexcept
Definition robin_hash.h:125
void set_hash(truncated_hash_type) noexcept
Definition robin_hash.h:130
Definition robin_hash.h:170
bucket_entry(bool last_bucket) noexcept
Definition robin_hash.h:184
std::int16_t distance_type
Definition robin_hash.h:175
void clear() noexcept
Definition robin_hash.h:243
bucket_entry & operator=(bucket_entry &&)=delete
bool empty() const noexcept
Definition robin_hash.h:251
bucket_entry(const bucket_entry &other) noexcept(std::is_nothrow_copy_constructible< value_type >::value)
Definition robin_hash.h:191
bucket_entry & operator=(const bucket_entry &other) noexcept(std::is_nothrow_copy_constructible< value_type >::value)
Definition robin_hash.h:221
unsigned char m_value[sizeof(value_type)]
Definition robin_hash.h:340
ValueType value_type
Definition robin_hash.h:174
void set_value_of_empty_bucket(distance_type dist_from_ideal_bucket, truncated_hash_type my_hash, Args &&...value_type_args)
Definition robin_hash.h:283
~bucket_entry() noexcept
Definition robin_hash.h:241
bucket_entry() noexcept
Definition robin_hash.h:177
static truncated_hash_type truncate_hash(std::size_t my_hash) noexcept
Definition robin_hash.h:318
distance_type m_dist_from_ideal_bucket
Definition robin_hash.h:338
bool last_bucket() const noexcept
Definition robin_hash.h:278
distance_type dist_from_ideal_bucket() const noexcept
Definition robin_hash.h:276
bool m_last_bucket
Definition robin_hash.h:339
bucket_entry_hash< StoreHash > bucket_hash
Definition robin_hash.h:171
void destroy_value() noexcept
Definition robin_hash.h:324
value_type & value() noexcept
Definition robin_hash.h:256
void swap_with_value_in_bucket(distance_type &dist_from_ideal_bucket, truncated_hash_type &my_hash, value_type &value)
Definition robin_hash.h:297
const value_type & value() const noexcept
Definition robin_hash.h:266
bucket_entry(bucket_entry &&other) noexcept(std::is_nothrow_move_constructible< value_type >::value)
Definition robin_hash.h:208
static const distance_type DIST_FROM_IDEAL_BUCKET_LIMIT
Definition robin_hash.h:332
static const distance_type EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET
Definition robin_hash.h:331
void set_as_last_bucket() noexcept
Definition robin_hash.h:280
const robin_hash::key_type & key() const
Definition robin_hash.h:485
robin_iterator & operator=(const robin_iterator &other)=default
reference operator*() const
Definition robin_hash.h:501
value_type & reference
Definition robin_hash.h:469
const U::value_type & value() const
Definition robin_hash.h:489
robin_iterator operator++(int)
Definition robin_hash.h:520
U::value_type & value() const
Definition robin_hash.h:496
typename std::conditional< IsConst, const bucket_entry *, bucket_entry * >::type bucket_entry_ptr
Definition robin_hash.h:460
const typename robin_hash::value_type value_type
Definition robin_hash.h:467
value_type * pointer
Definition robin_hash.h:470
robin_iterator(const robin_iterator &other)=default
robin_iterator(bucket_entry_ptr bucket) noexcept
Definition robin_hash.h:463
bucket_entry_ptr m_bucket
Definition robin_hash.h:539
robin_iterator & operator=(robin_iterator &&other)=default
robin_iterator(robin_iterator &&other)=default
friend bool operator!=(const robin_iterator &lhs, const robin_iterator &rhs)
Definition robin_hash.h:533
std::forward_iterator_tag iterator_category
Definition robin_hash.h:466
std::ptrdiff_t difference_type
Definition robin_hash.h:468
robin_iterator & operator++()
Definition robin_hash.h:505
pointer operator->() const
Definition robin_hash.h:503
friend bool operator==(const robin_iterator &lhs, const robin_iterator &rhs)
Definition robin_hash.h:528
Definition robin_hash.h:365
std::pair< const_iterator, const_iterator > equal_range(const K &key, std::size_t my_hash) const
Definition robin_hash.h:1013
key_equal key_eq() const
Definition robin_hash.h:1073
static const size_type DEFAULT_INIT_BUCKETS_SIZE
Definition robin_hash.h:1508
size_type m_nb_elements
Definition robin_hash.h:1562
static bool USE_STORED_HASH_ON_REHASH(size_type bucket_count)
Definition robin_hash.h:420
iterator find(const K &key)
Definition robin_hash.h:972
Hash hasher
Definition robin_hash.h:382
size_type m_bucket_count
Definition robin_hash.h:1560
const value_type * const_pointer
Definition robin_hash.h:388
iterator begin() noexcept
Definition robin_hash.h:664
const_iterator find(const K &key) const
Definition robin_hash.h:979
void deserialize_impl(Deserializer &deserializer, bool hash_compatible)
Definition robin_hash.h:1405
bucket_entry * static_empty_bucket_ptr() noexcept
Definition robin_hash.h:1535
static constexpr float DEFAULT_MIN_LOAD_FACTOR
Definition robin_hash.h:1514
void min_load_factor(float ml)
Definition robin_hash.h:1045
std::size_t next_bucket(std::size_t index) const noexcept
Definition robin_hash.h:1111
bool m_try_shrink_on_next_insert
Definition robin_hash.h:1578
static constexpr bool STORE_HASH
Definition robin_hash.h:398
bool m_grow_on_next_insert
Definition robin_hash.h:1569
static constexpr float MAXIMUM_MAX_LOAD_FACTOR
Definition robin_hash.h:1512
static const slz_size_type SERIALIZATION_PROTOCOL_VERSION
Definition robin_hash.h:1529
bool empty() const noexcept
Definition robin_hash.h:695
typename KeySelect::key_type key_type
Definition robin_hash.h:378
float min_load_factor() const
Definition robin_hash.h:1041
KeyEqual key_equal
Definition robin_hash.h:383
std::pair< iterator, bool > insert(P &&value)
Definition robin_hash.h:719
std::pair< const_iterator, const_iterator > equal_range(const K &key) const
Definition robin_hash.h:1007
iterator insert_or_assign(const_iterator hint, K &&key, M &&obj)
Definition robin_hash.h:761
void clear_and_shrink() noexcept
Definition robin_hash.h:1302
hasher hash_function() const
Definition robin_hash.h:1071
std::size_t hash_key(const K &key) const
Definition robin_hash.h:1094
iterator erase(const_iterator first, const_iterator last)
Definition robin_hash.h:823
value_type * pointer
Definition robin_hash.h:387
value_type & reference
Definition robin_hash.h:385
void insert_value_on_rehash(std::size_t ibucket, distance_type dist_from_ideal_bucket, truncated_hash_type my_hash, value_type &&value)
Definition robin_hash.h:1314
iterator find_impl(const K &key, std::size_t my_hash)
Definition robin_hash.h:1128
void rehash(size_type my_count)
Definition robin_hash.h:1057
std::pair< iterator, bool > try_emplace(K &&key, Args &&...args)
Definition robin_hash.h:783
std::pair< iterator, iterator > equal_range(const K &key, std::size_t my_hash)
Definition robin_hash.h:1001
void serialize(Serializer &serializer) const
Definition robin_hash.h:1083
robin_hash(size_type bucket_count, const Hash &my_hash, const KeyEqual &equal, const Allocator &alloc, float min_load_factor=DEFAULT_MIN_LOAD_FACTOR, float max_load_factor=DEFAULT_MAX_LOAD_FACTOR)
Definition robin_hash.h:575
bool contains(const K &key) const
Definition robin_hash.h:989
const_iterator find(const K &key, std::size_t my_hash) const
Definition robin_hash.h:984
robin_hash & operator=(const robin_hash &other)
Definition robin_hash.h:628
std::ptrdiff_t difference_type
Definition robin_hash.h:381
std::pair< iterator, bool > insert_or_assign(K &&key, M &&obj)
Definition robin_hash.h:751
std::pair< iterator, bool > emplace(Args &&...args)
Definition robin_hash.h:773
iterator insert_hint(const_iterator hint, P &&value)
Definition robin_hash.h:724
const U::value_type & at(const K &key) const
Definition robin_hash.h:935
void insert_value(std::size_t ibucket, distance_type dist_from_ideal_bucket, truncated_hash_type my_hash, value_type &&value)
Definition robin_hash.h:1235
static constexpr float MAXIMUM_MIN_LOAD_FACTOR
Definition robin_hash.h:1516
iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args)
Definition robin_hash.h:790
U::value_type & at(const K &key)
Definition robin_hash.h:920
U::value_type & operator[](K &&key)
Definition robin_hash.h:955
size_type max_size() const noexcept
Definition robin_hash.h:699
const_iterator end() const noexcept
Definition robin_hash.h:688
const_iterator cbegin() const noexcept
Definition robin_hash.h:676
void rehash_impl(size_type my_count)
Definition robin_hash.h:1278
allocator_type get_allocator() const
Definition robin_hash.h:659
iterator find(const K &key, std::size_t my_hash)
Definition robin_hash.h:974
ValueType value_type
Definition robin_hash.h:379
robin_iterator< false > iterator
Definition robin_hash.h:389
float load_factor() const
Definition robin_hash.h:1032
const value_type & const_reference
Definition robin_hash.h:386
iterator erase(iterator pos)
Definition robin_hash.h:803
robin_hash & operator=(robin_hash &&other)
Definition robin_hash.h:651
void erase_from_bucket(iterator pos)
Definition robin_hash.h:1152
iterator erase(const_iterator pos)
Definition robin_hash.h:821
size_type bucket_count() const
Definition robin_hash.h:1022
float m_min_load_factor
Definition robin_hash.h:1566
void deserialize(Deserializer &deserializer, bool hash_compatible)
Definition robin_hash.h:1088
const U::value_type & at(const K &key, std::size_t my_hash) const
Definition robin_hash.h:942
typename std::allocator_traits< allocator_type >::template rebind_alloc< bucket_entry > buckets_allocator
Definition robin_hash.h:439
void swap(robin_hash &other)
Definition robin_hash.h:897
size_type count(const K &key) const
Definition robin_hash.h:960
std::vector< bucket_entry, buckets_allocator > buckets_container_type
Definition robin_hash.h:441
const_iterator cend() const noexcept
Definition robin_hash.h:690
iterator end() noexcept
Definition robin_hash.h:686
float max_load_factor() const
Definition robin_hash.h:1043
bucket_entry * m_buckets
Definition robin_hash.h:1554
const_iterator find_impl(const K &key, std::size_t my_hash) const
Definition robin_hash.h:1133
void max_load_factor(float ml)
Definition robin_hash.h:1050
static constexpr float DEFAULT_MAX_LOAD_FACTOR
Definition robin_hash.h:1510
U::value_type & at(const K &key, std::size_t my_hash)
Definition robin_hash.h:927
void insert_value_impl(std::size_t ibucket, distance_type dist_from_ideal_bucket, truncated_hash_type my_hash, value_type &value)
Definition robin_hash.h:1249
static constexpr float MINIMUM_MAX_LOAD_FACTOR
Definition robin_hash.h:1511
robin_hash(const robin_hash &other)
Definition robin_hash.h:598
std::size_t bucket_for_hash(std::size_t my_hash) const
Definition robin_hash.h:1101
void insert(InputIt first, InputIt last)
Definition robin_hash.h:733
size_type size() const noexcept
Definition robin_hash.h:697
float m_max_load_factor
Definition robin_hash.h:1567
size_type erase(const K &key)
Definition robin_hash.h:881
void insert_value(std::size_t ibucket, distance_type dist_from_ideal_bucket, truncated_hash_type my_hash, Args &&...value_type_args)
Definition robin_hash.h:1228
std::pair< iterator, bool > insert_impl(const K &key, Args &&...value_type_args)
Definition robin_hash.h:1182
size_type m_load_threshold
Definition robin_hash.h:1564
typename bucket_entry::distance_type distance_type
Definition robin_hash.h:437
bool rehash_on_extreme_load(distance_type curr_dist_from_ideal_bucket)
Definition robin_hash.h:1341
size_type erase(const K &key, std::size_t my_hash)
Definition robin_hash.h:883
void serialize_impl(Serializer &serializer) const
Definition robin_hash.h:1364
typename std::integral_constant< bool, !std::is_same< U, void >::value > has_mapped_type
Definition robin_hash.h:368
bool contains(const K &key, std::size_t my_hash) const
Definition robin_hash.h:991
std::pair< iterator, iterator > equal_range(const K &key)
Definition robin_hash.h:996
bool compare_keys(const K1 &key1, const K2 &key2) const
Definition robin_hash.h:1096
robin_iterator< true > const_iterator
Definition robin_hash.h:390
std::size_t size_type
Definition robin_hash.h:380
void reserve(size_type my_count)
Definition robin_hash.h:1063
static constexpr float MINIMUM_MIN_LOAD_FACTOR
Definition robin_hash.h:1515
const_iterator begin() const noexcept
Definition robin_hash.h:674
iterator mutable_iterator(const_iterator pos)
Definition robin_hash.h:1078
Allocator allocator_type
Definition robin_hash.h:384
static constexpr bool USE_STORED_HASH_ON_LOOKUP
Definition robin_hash.h:412
size_type max_bucket_count() const
Definition robin_hash.h:1024
iterator emplace_hint(const_iterator hint, Args &&...args)
Definition robin_hash.h:778
void clear() noexcept
Definition robin_hash.h:704
size_type count(const K &key, std::size_t my_hash) const
Definition robin_hash.h:962
buckets_container_type m_buckets_data
Definition robin_hash.h:1543
robin_hash(robin_hash &&other) noexcept(std::is_nothrow_move_constructible< Hash >::value &&std::is_nothrow_move_constructible< KeyEqual >::value &&std::is_nothrow_move_constructible< GrowthPolicy >::value &&std::is_nothrow_move_constructible< buckets_container_type >::value)
Definition robin_hash.h:609
Definition robin_growth_policy.h:84
Definition robin_hash.h:46
static T numeric_cast(U value, const char *error_message="numeric_cast() failed.")
Definition robin_hash.h:79
std::uint64_t slz_size_type
Definition robin_hash.h:111
static T deserialize_value(Deserializer &deserializer)
Definition robin_hash.h:95
std::uint32_t truncated_hash_type
Definition robin_hash.h:116
const T & clamp(const T &v, const T &lo, const T &hi)
Definition robin_hash.h:73
Definition bhopscotch_map.h:38
#define TSL_RH_LIKELY(exp)
Definition robin_growth_policy.h:69
#define TSL_RH_THROW_OR_TERMINATE(ex, msg)
Definition robin_growth_policy.h:62
#define tsl_rh_assert(expr)
Definition robin_growth_policy.h:41
#define TSL_RH_UNUSED(x)
Definition robin_growth_policy.h:72
Definition robin_hash.h:49
void type
Definition robin_hash.h:50