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