robin-map
robin_hash.h
Go to the documentation of this file.
1 /**
2  * MIT License
3  *
4  * Copyright (c) 2017 Tessil
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 all
14  * 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 
28 #include <algorithm>
29 #include <cassert>
30 #include <cmath>
31 #include <cstddef>
32 #include <cstdint>
33 #include <exception>
34 #include <iterator>
35 #include <limits>
36 #include <memory>
37 #include <stdexcept>
38 #include <tuple>
39 #include <type_traits>
40 #include <utility>
41 #include <vector>
43 
44 
45 namespace tsl {
46 
47 namespace detail_robin_hash {
48 
49 template<typename T>
50 struct make_void {
51  using type = void;
52 };
53 
54 template<typename T, typename = void>
55 struct has_is_transparent: std::false_type {
56 };
57 
58 template<typename T>
59 struct has_is_transparent<T, typename make_void<typename T::is_transparent>::type>: std::true_type {
60 };
61 
62 template<typename U>
63 struct is_power_of_two_policy: std::false_type {
64 };
65 
66 template<std::size_t GrowthFactor>
67 struct is_power_of_two_policy<tsl::rh::power_of_two_growth_policy<GrowthFactor>>: std::true_type {
68 };
69 
70 // Only available in C++17, we need to be compatible with C++11
71 template<class T>
72 const T& clamp( const T& v, const T& lo, const T& hi) {
73  return std::min(hi, std::max(lo, v));
74 }
75 
76 
77 using truncated_hash_type = std::uint_least32_t;
78 
79 /**
80  * Helper class that stores a truncated hash if StoreHash is true and nothing otherwise.
81  */
82 template<bool StoreHash>
83 class bucket_entry_hash {
84 public:
85  bool bucket_hash_equal(std::size_t /*hash*/) const noexcept {
86  return true;
87  }
88 
89  truncated_hash_type truncated_hash() const noexcept {
90  return 0;
91  }
92 
93 protected:
94  void set_hash(truncated_hash_type /*hash*/) noexcept {
95  }
96 };
97 
98 template<>
99 class bucket_entry_hash<true> {
100 public:
101  bool bucket_hash_equal(std::size_t hash) const noexcept {
102  return m_hash == truncated_hash_type(hash);
103  }
104 
105  truncated_hash_type truncated_hash() const noexcept {
106  return m_hash;
107  }
108 
109 protected:
110  void set_hash(truncated_hash_type hash) noexcept {
111  m_hash = truncated_hash_type(hash);
112  }
113 
114 private:
115  truncated_hash_type m_hash;
116 };
117 
118 
119 /**
120  * Each bucket entry has:
121  * - A value of type `ValueType`.
122  * - An integer to store how far the value of the bucket, if any, is from its ideal bucket
123  * (ex: if the current bucket 5 has the value 'foo' and `hash('foo') % nb_buckets` == 3,
124  * `dist_from_ideal_bucket()` will return 2 as the current value of the bucket is two
125  * buckets away from its ideal bucket)
126  * If there is no value in the bucket (i.e. `empty()` is true) `dist_from_ideal_bucket()` will be < 0.
127  * - A marker which tells us if the bucket is the last bucket of the bucket array (useful for the
128  * iterator of the hash table).
129  * - If `StoreHash` is true, 32 bits of the hash of the value, if any, are also stored in the bucket.
130  * If the size of the hash is more than 32 bits, it is truncated. We don't store the full hash
131  * as storing the hash is a potential opportunity to use the unused space due to the alignement
132  * of the bucket_entry structure. We can thus potentially store the hash without any extra space
133  * (which would not be possible with 64 bits of the hash).
134  */
135 template<typename ValueType, bool StoreHash>
136 class bucket_entry: public bucket_entry_hash<StoreHash> {
137  using bucket_hash = bucket_entry_hash<StoreHash>;
138 
139 public:
140  using value_type = ValueType;
141  using distance_type = std::int_least16_t;
142 
143 
144  bucket_entry() noexcept: bucket_hash(), m_dist_from_ideal_bucket(EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET),
145  m_last_bucket(false)
146  {
147  tsl_rh_assert(empty());
148  }
149 
150  bucket_entry(bool last_bucket) noexcept: bucket_hash(), m_dist_from_ideal_bucket(EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET),
151  m_last_bucket(last_bucket)
152  {
153  tsl_rh_assert(empty());
154  }
155 
156  bucket_entry(const bucket_entry& other) noexcept(std::is_nothrow_copy_constructible<value_type>::value):
157  bucket_hash(other),
158  m_dist_from_ideal_bucket(EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET),
159  m_last_bucket(other.m_last_bucket)
160  {
161  if(!other.empty()) {
162  ::new (static_cast<void*>(std::addressof(m_value))) value_type(other.value());
163  m_dist_from_ideal_bucket = other.m_dist_from_ideal_bucket;
164  }
165  }
166 
167  /**
168  * Never really used, but still necessary as we must call resize on an empty `std::vector<bucket_entry>`.
169  * and we need to support move-only types. See robin_hash constructor for details.
170  */
171  bucket_entry(bucket_entry&& other) noexcept(std::is_nothrow_move_constructible<value_type>::value):
172  bucket_hash(std::move(other)),
173  m_dist_from_ideal_bucket(EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET),
174  m_last_bucket(other.m_last_bucket)
175  {
176  if(!other.empty()) {
177  ::new (static_cast<void*>(std::addressof(m_value))) value_type(std::move(other.value()));
178  m_dist_from_ideal_bucket = other.m_dist_from_ideal_bucket;
179  }
180  }
181 
182  bucket_entry& operator=(const bucket_entry& other)
183  noexcept(std::is_nothrow_copy_constructible<value_type>::value)
184  {
185  if(this != &other) {
186  clear();
187 
188  bucket_hash::operator=(other);
189  if(!other.empty()) {
190  ::new (static_cast<void*>(std::addressof(m_value))) value_type(other.value());
191  }
192 
193  m_dist_from_ideal_bucket = other.m_dist_from_ideal_bucket;
194  m_last_bucket = other.m_last_bucket;
195  }
196 
197  return *this;
198  }
199 
200  bucket_entry& operator=(bucket_entry&& ) = delete;
201 
202  ~bucket_entry() noexcept {
203  clear();
204  }
205 
206  void clear() noexcept {
207  if(!empty()) {
208  destroy_value();
209  m_dist_from_ideal_bucket = EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET;
210  }
211  }
212 
213  bool empty() const noexcept {
214  return m_dist_from_ideal_bucket == EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET;
215  }
216 
217  value_type& value() noexcept {
218  tsl_rh_assert(!empty());
219  return *reinterpret_cast<value_type*>(std::addressof(m_value));
220  }
221 
222  const value_type& value() const noexcept {
223  tsl_rh_assert(!empty());
224  return *reinterpret_cast<const value_type*>(std::addressof(m_value));
225  }
226 
227  distance_type dist_from_ideal_bucket() const noexcept {
228  return m_dist_from_ideal_bucket;
229  }
230 
231  bool last_bucket() const noexcept {
232  return m_last_bucket;
233  }
234 
235  void set_as_last_bucket() noexcept {
236  m_last_bucket = true;
237  }
238 
239  template<typename... Args>
240  void set_value_of_empty_bucket(distance_type dist_from_ideal_bucket,
241  truncated_hash_type hash, Args&&... value_type_args)
242  {
243  tsl_rh_assert(dist_from_ideal_bucket >= 0);
244  tsl_rh_assert(empty());
245 
246  ::new (static_cast<void*>(std::addressof(m_value))) value_type(std::forward<Args>(value_type_args)...);
247  this->set_hash(hash);
248  m_dist_from_ideal_bucket = dist_from_ideal_bucket;
249 
250  tsl_rh_assert(!empty());
251  }
252 
253  void swap_with_value_in_bucket(distance_type& dist_from_ideal_bucket,
254  truncated_hash_type& hash, value_type& value)
255  {
256  tsl_rh_assert(!empty());
257 
258  using std::swap;
259  swap(value, this->value());
260  swap(dist_from_ideal_bucket, m_dist_from_ideal_bucket);
261 
262  // Avoid warning of unused variable if StoreHash is false
263  (void) hash;
264  if(StoreHash) {
265  const truncated_hash_type tmp_hash = this->truncated_hash();
266  this->set_hash(hash);
267  hash = tmp_hash;
268  }
269  }
270 
271  static truncated_hash_type truncate_hash(std::size_t hash) noexcept {
272  return truncated_hash_type(hash);
273  }
274 
275 private:
276  void destroy_value() noexcept {
277  tsl_rh_assert(!empty());
278  value().~value_type();
279  }
280 
281 private:
282  using storage = typename std::aligned_storage<sizeof(value_type), alignof(value_type)>::type;
283 
284  static const distance_type EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET = -1;
285 
286  distance_type m_dist_from_ideal_bucket;
287  bool m_last_bucket;
288  storage m_value;
289 };
290 
291 
292 
293 /**
294  * Internal common class used by `robin_map` and `robin_set`.
295  *
296  * ValueType is what will be stored by `robin_hash` (usually `std::pair<Key, T>` for map and `Key` for set).
297  *
298  * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in parameter and returns a
299  * reference to the key.
300  *
301  * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in parameter and returns a
302  * reference to the value. `ValueSelect` should be void if there is no value (in a set for example).
303  *
304  * The strong exception guarantee only holds if the expression
305  * `std::is_nothrow_swappable<ValueType>::value && std::is_nothrow_move_constructible<ValueType>::value` is true.
306  *
307  * Behaviour is undefined if the destructor of `ValueType` throws.
308  */
309 template<class ValueType,
310  class KeySelect,
311  class ValueSelect,
312  class Hash,
313  class KeyEqual,
314  class Allocator,
315  bool StoreHash,
316  class GrowthPolicy>
317 class robin_hash: private Hash, private KeyEqual, private GrowthPolicy {
318 private:
319  template<typename U>
320  using has_mapped_type = typename std::integral_constant<bool, !std::is_same<U, void>::value>;
321 
322  static_assert(noexcept(std::declval<GrowthPolicy>().bucket_for_hash(std::size_t(0))), "GrowthPolicy::bucket_for_hash must be noexcept.");
323  static_assert(noexcept(std::declval<GrowthPolicy>().clear()), "GrowthPolicy::clear must be noexcept.");
324 
325 public:
326  template<bool IsConst>
328 
329  using key_type = typename KeySelect::key_type;
330  using value_type = ValueType;
331  using size_type = std::size_t;
332  using difference_type = std::ptrdiff_t;
333  using hasher = Hash;
334  using key_equal = KeyEqual;
335  using allocator_type = Allocator;
336  using reference = value_type&;
337  using const_reference = const value_type&;
338  using pointer = value_type*;
339  using const_pointer = const value_type*;
340  using iterator = robin_iterator<false>;
341  using const_iterator = robin_iterator<true>;
342 
343 
344 private:
345  /**
346  * Either store the hash because we are asked by the `StoreHash` template parameter
347  * or store the hash because it doesn't cost us anything in size and can be used to speed up rehash.
348  */
349  static constexpr bool STORE_HASH = StoreHash ||
350  (
351  (sizeof(tsl::detail_robin_hash::bucket_entry<value_type, true>) ==
352  sizeof(tsl::detail_robin_hash::bucket_entry<value_type, false>))
353  &&
354  (sizeof(std::size_t) == sizeof(truncated_hash_type) ||
355  is_power_of_two_policy<GrowthPolicy>::value)
356  &&
357  // Don't store the hash for primitive types with default hash.
358  (!std::is_arithmetic<key_type>::value ||
359  !std::is_same<Hash, std::hash<key_type>>::value)
360  );
361 
362  /**
363  * Only use the stored hash on lookup if we are explictly asked. We are not sure how slow
364  * the KeyEqual operation is. An extra comparison may slow things down with a fast KeyEqual.
365  */
366  static constexpr bool USE_STORED_HASH_ON_LOOKUP = StoreHash;
367 
368  /**
369  * We can only use the hash on rehash if the size of the hash type is the same as the stored one or
370  * if we use a power of two modulo. In the case of the power of two modulo, we just mask
371  * the least significant bytes, we just have to check that the truncated_hash_type didn't truncated
372  * more bytes.
373  */
374  static bool USE_STORED_HASH_ON_REHASH(size_type bucket_count) {
375  (void) bucket_count;
376  if(STORE_HASH && sizeof(std::size_t) == sizeof(truncated_hash_type)) {
377  return true;
378  }
379  else if(STORE_HASH && is_power_of_two_policy<GrowthPolicy>::value) {
380  tsl_rh_assert(bucket_count > 0);
381  return (bucket_count - 1) <= std::numeric_limits<truncated_hash_type>::max();
382  }
383  else {
384  return false;
385  }
386  }
387 
388  using bucket_entry = tsl::detail_robin_hash::bucket_entry<value_type, STORE_HASH>;
389  using distance_type = typename bucket_entry::distance_type;
390 
391  using buckets_allocator = typename std::allocator_traits<allocator_type>::template rebind_alloc<bucket_entry>;
392  using buckets_container_type = std::vector<bucket_entry, buckets_allocator>;
393 
394 
395 public:
396  /**
397  * The 'operator*()' and 'operator->()' methods return a const reference and const pointer respectively to the
398  * stored value type.
399  *
400  * In case of a map, to get a mutable reference to the value associated to a key (the '.second' in the
401  * stored pair), you have to call 'value()'.
402  *
403  * The main reason for this is that if we returned a `std::pair<Key, T>&` instead
404  * of a `const std::pair<Key, T>&`, the user may modify the key which will put the map in a undefined state.
405  */
406  template<bool IsConst>
407  class robin_iterator {
408  friend class robin_hash;
409 
410  private:
411  using bucket_entry_ptr = typename std::conditional<IsConst,
412  const bucket_entry*,
413  bucket_entry*>::type;
414 
415 
416  robin_iterator(bucket_entry_ptr bucket) noexcept: m_bucket(bucket) {
417  }
418 
419  public:
420  using iterator_category = std::forward_iterator_tag;
421  using value_type = const typename robin_hash::value_type;
422  using difference_type = std::ptrdiff_t;
423  using reference = value_type&;
424  using pointer = value_type*;
425 
426 
427  robin_iterator() noexcept {
428  }
429 
430  // Copy constructor from iterator to const_iterator.
431  template<bool TIsConst = IsConst, typename std::enable_if<TIsConst>::type* = nullptr>
432  robin_iterator(const robin_iterator<!TIsConst>& other) noexcept: m_bucket(other.m_bucket) {
433  }
434 
435  robin_iterator(const robin_iterator& other) = default;
436  robin_iterator(robin_iterator&& other) = default;
437  robin_iterator& operator=(const robin_iterator& other) = default;
438  robin_iterator& operator=(robin_iterator&& other) = default;
439 
440  const typename robin_hash::key_type& key() const {
441  return KeySelect()(m_bucket->value());
442  }
443 
444  template<class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value && IsConst>::type* = nullptr>
445  const typename U::value_type& value() const {
446  return U()(m_bucket->value());
447  }
448 
449  template<class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value && !IsConst>::type* = nullptr>
450  typename U::value_type& value() {
451  return U()(m_bucket->value());
452  }
453 
454  reference operator*() const {
455  return m_bucket->value();
456  }
457 
458  pointer operator->() const {
459  return std::addressof(m_bucket->value());
460  }
461 
463  while(true) {
464  if(m_bucket->last_bucket()) {
465  ++m_bucket;
466  return *this;
467  }
468 
469  ++m_bucket;
470  if(!m_bucket->empty()) {
471  return *this;
472  }
473  }
474  }
475 
477  robin_iterator tmp(*this);
478  ++*this;
479 
480  return tmp;
481  }
482 
483  friend bool operator==(const robin_iterator& lhs, const robin_iterator& rhs) {
484  return lhs.m_bucket == rhs.m_bucket;
485  }
486 
487  friend bool operator!=(const robin_iterator& lhs, const robin_iterator& rhs) {
488  return !(lhs == rhs);
489  }
490 
491  private:
492  bucket_entry_ptr m_bucket;
493  };
494 
495 
496 public:
497 #if defined(__cplusplus) && __cplusplus >= 201402L
499  const Hash& hash,
500  const KeyEqual& equal,
501  const Allocator& alloc,
504  Hash(hash),
505  KeyEqual(equal),
508  [&]() {
511  "The map exceeds its maximum bucket count.");
512  }
513 
514  return bucket_count;
515  }(), alloc
516  ),
519  m_nb_elements(0),
520  m_grow_on_next_insert(false),
522  {
523  if(m_bucket_count > 0) {
526  }
527 
530  }
531 #else
532  /**
533  * C++11 doesn't support the creation of a std::vector with a custom allocator and 'count' default-inserted elements.
534  * The needed contructor `explicit vector(size_type count, const Allocator& alloc = Allocator());` is only
535  * available in C++14 and later. We thus must resize after using the `vector(const Allocator& alloc)` constructor.
536  *
537  * We can't use `vector(size_type count, const T& value, const Allocator& alloc)` as it requires the
538  * value T to be copyable.
539  */
540  robin_hash(size_type bucket_count,
541  const Hash& hash,
542  const KeyEqual& equal,
543  const Allocator& alloc,
544  float min_load_factor = DEFAULT_MIN_LOAD_FACTOR,
545  float max_load_factor = DEFAULT_MAX_LOAD_FACTOR):
546  Hash(hash),
547  KeyEqual(equal),
548  GrowthPolicy(bucket_count),
549  m_buckets_data(alloc),
550  m_buckets(static_empty_bucket_ptr()),
551  m_bucket_count(bucket_count),
552  m_nb_elements(0),
553  m_grow_on_next_insert(false),
554  m_try_skrink_on_next_insert(false)
555  {
556  if(bucket_count > max_bucket_count()) {
557  TSL_RH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maxmimum bucket count.");
558  }
559 
560  if(m_bucket_count > 0) {
561  m_buckets_data.resize(m_bucket_count);
562  m_buckets = m_buckets_data.data();
563 
564  tsl_rh_assert(!m_buckets_data.empty());
565  m_buckets_data.back().set_as_last_bucket();
566  }
567 
568  this->min_load_factor(min_load_factor);
569  this->max_load_factor(max_load_factor);
570  }
571 #endif
572 
573  robin_hash(const robin_hash& other): Hash(other),
574  KeyEqual(other),
575  GrowthPolicy(other),
576  m_buckets_data(other.m_buckets_data),
577  m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():m_buckets_data.data()),
578  m_bucket_count(other.m_bucket_count),
579  m_nb_elements(other.m_nb_elements),
580  m_load_threshold(other.m_load_threshold),
581  m_max_load_factor(other.m_max_load_factor),
582  m_grow_on_next_insert(other.m_grow_on_next_insert),
583  m_min_load_factor(other.m_min_load_factor),
584  m_try_skrink_on_next_insert(other.m_try_skrink_on_next_insert)
585  {
586  }
587 
592  : Hash(std::move(static_cast<Hash&>(other))),
593  KeyEqual(std::move(static_cast<KeyEqual&>(other))),
594  GrowthPolicy(std::move(static_cast<GrowthPolicy&>(other))),
595  m_buckets_data(std::move(other.m_buckets_data)),
596  m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():m_buckets_data.data()),
597  m_bucket_count(other.m_bucket_count),
598  m_nb_elements(other.m_nb_elements),
599  m_load_threshold(other.m_load_threshold),
600  m_max_load_factor(other.m_max_load_factor),
601  m_grow_on_next_insert(other.m_grow_on_next_insert),
602  m_min_load_factor(other.m_min_load_factor),
603  m_try_skrink_on_next_insert(other.m_try_skrink_on_next_insert)
604  {
605  other.GrowthPolicy::clear();
606  other.m_buckets_data.clear();
607  other.m_buckets = static_empty_bucket_ptr();
608  other.m_bucket_count = 0;
609  other.m_nb_elements = 0;
610  other.m_load_threshold = 0;
611  other.m_grow_on_next_insert = false;
612  other.m_try_skrink_on_next_insert = false;
613  }
614 
615  robin_hash& operator=(const robin_hash& other) {
616  if(&other != this) {
617  Hash::operator=(other);
618  KeyEqual::operator=(other);
619  GrowthPolicy::operator=(other);
620 
621  m_buckets_data = other.m_buckets_data;
622  m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr():
623  m_buckets_data.data();
624  m_bucket_count = other.m_bucket_count;
625  m_nb_elements = other.m_nb_elements;
626 
627  m_load_threshold = other.m_load_threshold;
628  m_max_load_factor = other.m_max_load_factor;
629  m_grow_on_next_insert = other.m_grow_on_next_insert;
630 
631  m_min_load_factor = other.m_min_load_factor;
632  m_try_skrink_on_next_insert = other.m_try_skrink_on_next_insert;
633  }
634 
635  return *this;
636  }
637 
639  other.swap(*this);
640  other.clear();
641 
642  return *this;
643  }
644 
645  allocator_type get_allocator() const {
646  return m_buckets_data.get_allocator();
647  }
648 
649 
650  /*
651  * Iterators
652  */
653  iterator begin() noexcept {
654  std::size_t i = 0;
655  while(i < m_bucket_count && m_buckets[i].empty()) {
656  i++;
657  }
658 
659  return iterator(m_buckets + i);
660  }
661 
662  const_iterator begin() const noexcept {
663  return cbegin();
664  }
665 
666  const_iterator cbegin() const noexcept {
667  std::size_t i = 0;
668  while(i < m_bucket_count && m_buckets[i].empty()) {
669  i++;
670  }
671 
672  return const_iterator(m_buckets + i);
673  }
674 
675  iterator end() noexcept {
676  return iterator(m_buckets + m_bucket_count);
677  }
678 
679  const_iterator end() const noexcept {
680  return cend();
681  }
682 
683  const_iterator cend() const noexcept {
684  return const_iterator(m_buckets + m_bucket_count);
685  }
686 
687 
688  /*
689  * Capacity
690  */
691  bool empty() const noexcept {
692  return m_nb_elements == 0;
693  }
694 
695  size_type size() const noexcept {
696  return m_nb_elements;
697  }
698 
699  size_type max_size() const noexcept {
700  return m_buckets_data.max_size();
701  }
702 
703  /*
704  * Modifiers
705  */
706  void clear() noexcept {
707  for(auto& bucket: m_buckets_data) {
708  bucket.clear();
709  }
710 
711  m_nb_elements = 0;
712  m_grow_on_next_insert = false;
713  }
714 
715 
716 
717  template<typename P>
718  std::pair<iterator, bool> insert(P&& value) {
719  return insert_impl(KeySelect()(value), std::forward<P>(value));
720  }
721 
722  template<typename P>
723  iterator insert_hint(const_iterator hint, P&& value) {
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>
732  void insert(InputIt first, InputIt last) {
733  if(std::is_base_of<std::forward_iterator_tag,
734  typename std::iterator_traits<InputIt>::iterator_category>::value)
735  {
736  const auto nb_elements_insert = std::distance(first, last);
737  const size_type nb_free_buckets = m_load_threshold - size();
738  tsl_rh_assert(m_load_threshold >= size());
739 
740  if(nb_elements_insert > 0 && nb_free_buckets < size_type(nb_elements_insert)) {
741  reserve(size() + size_type(nb_elements_insert));
742  }
743  }
744 
745  for(; first != last; ++first) {
746  insert(*first);
747  }
748  }
749 
750 
751 
752  template<class K, class M>
753  std::pair<iterator, bool> insert_or_assign(K&& key, M&& obj) {
754  auto it = try_emplace(std::forward<K>(key), std::forward<M>(obj));
755  if(!it.second) {
756  it.first.value() = std::forward<M>(obj);
757  }
758 
759  return it;
760  }
761 
762  template<class K, class M>
763  iterator insert_or_assign(const_iterator hint, K&& key, M&& obj) {
764  if(hint != cend() && compare_keys(KeySelect()(*hint), key)) {
765  auto it = mutable_iterator(hint);
766  it.value() = std::forward<M>(obj);
767 
768  return it;
769  }
770 
771  return insert_or_assign(std::forward<K>(key), std::forward<M>(obj)).first;
772  }
773 
774 
775  template<class... Args>
776  std::pair<iterator, bool> emplace(Args&&... args) {
777  return insert(value_type(std::forward<Args>(args)...));
778  }
779 
780  template<class... Args>
781  iterator emplace_hint(const_iterator hint, Args&&... args) {
782  return insert_hint(hint, value_type(std::forward<Args>(args)...));
783  }
784 
785 
786 
787  template<class K, class... Args>
788  std::pair<iterator, bool> try_emplace(K&& key, Args&&... args) {
789  return insert_impl(key, std::piecewise_construct,
790  std::forward_as_tuple(std::forward<K>(key)),
791  std::forward_as_tuple(std::forward<Args>(args)...));
792  }
793 
794  template<class K, class... Args>
795  iterator try_emplace_hint(const_iterator hint, K&& key, Args&&... args) {
796  if(hint != cend() && compare_keys(KeySelect()(*hint), key)) {
797  return mutable_iterator(hint);
798  }
799 
800  return try_emplace(std::forward<K>(key), std::forward<Args>(args)...).first;
801  }
802 
803  /**
804  * Here to avoid `template<class K> size_type erase(const K& key)` being used when
805  * we use an `iterator` instead of a `const_iterator`.
806  */
807  iterator erase(iterator pos) {
808  erase_from_bucket(pos);
809 
810  /**
811  * Erase bucket used a backward shift after clearing the bucket.
812  * Check if there is a new value in the bucket, if not get the next non-empty.
813  */
814  if(pos.m_bucket->empty()) {
815  ++pos;
816  }
817 
818  m_try_skrink_on_next_insert = true;
819 
820  return pos;
821  }
822 
823  iterator erase(const_iterator pos) {
824  return erase(mutable_iterator(pos));
825  }
826 
827  iterator erase(const_iterator first, const_iterator last) {
828  if(first == last) {
829  return mutable_iterator(first);
830  }
831 
832  auto first_mutable = mutable_iterator(first);
833  auto last_mutable = mutable_iterator(last);
834  for(auto it = first_mutable.m_bucket; it != last_mutable.m_bucket; ++it) {
835  if(!it->empty()) {
836  it->clear();
837  m_nb_elements--;
838  }
839  }
840 
841  if(last_mutable == end()) {
842  return end();
843  }
844 
845 
846  /*
847  * Backward shift on the values which come after the deleted values.
848  * We try to move the values closer to their ideal bucket.
849  */
850  std::size_t icloser_bucket = static_cast<std::size_t>(first_mutable.m_bucket - m_buckets);
851  std::size_t ito_move_closer_value = static_cast<std::size_t>(last_mutable.m_bucket - m_buckets);
852  tsl_rh_assert(ito_move_closer_value > icloser_bucket);
853 
854  const std::size_t ireturn_bucket = ito_move_closer_value -
855  std::min(ito_move_closer_value - icloser_bucket,
856  std::size_t(m_buckets[ito_move_closer_value].dist_from_ideal_bucket()));
857 
858  while(ito_move_closer_value < m_bucket_count && m_buckets[ito_move_closer_value].dist_from_ideal_bucket() > 0) {
859  icloser_bucket = 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 
864  tsl_rh_assert(m_buckets[icloser_bucket].empty());
865  const distance_type new_distance = 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(new_distance,
868  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 
873  ++icloser_bucket;
874  ++ito_move_closer_value;
875  }
876 
877  m_try_skrink_on_next_insert = true;
878 
879  return iterator(m_buckets + ireturn_bucket);
880  }
881 
882 
883  template<class K>
884  size_type erase(const K& key) {
885  return erase(key, hash_key(key));
886  }
887 
888  template<class K>
889  size_type erase(const K& key, std::size_t hash) {
890  auto it = find(key, hash);
891  if(it != end()) {
892  erase_from_bucket(it);
893  m_try_skrink_on_next_insert = true;
894 
895  return 1;
896  }
897  else {
898  return 0;
899  }
900  }
901 
902 
903 
904 
905 
906  void swap(robin_hash& other) {
907  using std::swap;
908 
909  swap(static_cast<Hash&>(*this), static_cast<Hash&>(other));
910  swap(static_cast<KeyEqual&>(*this), static_cast<KeyEqual&>(other));
911  swap(static_cast<GrowthPolicy&>(*this), static_cast<GrowthPolicy&>(other));
912  swap(m_buckets_data, other.m_buckets_data);
913  swap(m_buckets, other.m_buckets);
914  swap(m_bucket_count, other.m_bucket_count);
915  swap(m_nb_elements, other.m_nb_elements);
916  swap(m_load_threshold, other.m_load_threshold);
917  swap(m_max_load_factor, other.m_max_load_factor);
918  swap(m_grow_on_next_insert, other.m_grow_on_next_insert);
919  swap(m_min_load_factor, other.m_min_load_factor);
920  swap(m_try_skrink_on_next_insert, other.m_try_skrink_on_next_insert);
921  }
922 
923 
924  /*
925  * Lookup
926  */
927  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
928  typename U::value_type& at(const K& key) {
929  return at(key, hash_key(key));
930  }
931 
932  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
933  typename U::value_type& at(const K& key, std::size_t hash) {
934  return const_cast<typename U::value_type&>(static_cast<const robin_hash*>(this)->at(key, hash));
935  }
936 
937 
938  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
939  const typename U::value_type& at(const K& key) const {
940  return at(key, hash_key(key));
941  }
942 
943  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
944  const typename U::value_type& at(const K& key, std::size_t hash) const {
945  auto it = find(key, 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, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>
955  typename U::value_type& operator[](K&& key) {
956  return try_emplace(std::forward<K>(key)).first.value();
957  }
958 
959 
960  template<class K>
961  size_type count(const K& key) const {
962  return count(key, hash_key(key));
963  }
964 
965  template<class K>
966  size_type count(const K& key, std::size_t hash) const {
967  if(find(key, hash) != cend()) {
968  return 1;
969  }
970  else {
971  return 0;
972  }
973  }
974 
975 
976  template<class K>
977  iterator find(const K& key) {
978  return find_impl(key, hash_key(key));
979  }
980 
981  template<class K>
982  iterator find(const K& key, std::size_t hash) {
983  return find_impl(key, hash);
984  }
985 
986 
987  template<class K>
988  const_iterator find(const K& key) const {
989  return find_impl(key, hash_key(key));
990  }
991 
992  template<class K>
993  const_iterator find(const K& key, std::size_t hash) const {
994  return find_impl(key, hash);
995  }
996 
997 
998  template<class K>
999  std::pair<iterator, iterator> equal_range(const K& key) {
1000  return equal_range(key, hash_key(key));
1001  }
1002 
1003  template<class K>
1004  std::pair<iterator, iterator> equal_range(const K& key, std::size_t hash) {
1005  iterator it = find(key, hash);
1006  return std::make_pair(it, (it == end())?it:std::next(it));
1007  }
1008 
1009 
1010  template<class K>
1011  std::pair<const_iterator, const_iterator> equal_range(const K& key) const {
1012  return equal_range(key, hash_key(key));
1013  }
1014 
1015  template<class K>
1016  std::pair<const_iterator, const_iterator> equal_range(const K& key, std::size_t hash) const {
1017  const_iterator it = find(key, hash);
1018  return std::make_pair(it, (it == cend())?it:std::next(it));
1019  }
1020 
1021  /*
1022  * Bucket interface
1023  */
1024  size_type bucket_count() const {
1025  return m_bucket_count;
1026  }
1027 
1028  size_type max_bucket_count() const {
1029  return std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size());
1030  }
1031 
1032  /*
1033  * Hash policy
1034  */
1035  float load_factor() const {
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 {
1044  return m_min_load_factor;
1045  }
1046 
1047  float max_load_factor() const {
1048  return m_max_load_factor;
1049  }
1050 
1051  void min_load_factor(float ml) {
1052  m_min_load_factor = clamp(ml, float(MINIMUM_MIN_LOAD_FACTOR),
1053  float(MAXIMUM_MIN_LOAD_FACTOR));
1054  }
1055 
1056  void max_load_factor(float ml) {
1057  m_max_load_factor = clamp(ml, float(MINIMUM_MAX_LOAD_FACTOR),
1058  float(MAXIMUM_MAX_LOAD_FACTOR));
1059  m_load_threshold = size_type(float(bucket_count())*m_max_load_factor);
1060  }
1061 
1062  void rehash(size_type count) {
1063  count = std::max(count, size_type(std::ceil(float(size())/max_load_factor())));
1064  rehash_impl(count);
1065  }
1066 
1067  void reserve(size_type count) {
1068  rehash(size_type(std::ceil(float(count)/max_load_factor())));
1069  }
1070 
1071  /*
1072  * Observers
1073  */
1074  hasher hash_function() const {
1075  return static_cast<const Hash&>(*this);
1076  }
1077 
1078  key_equal key_eq() const {
1079  return static_cast<const KeyEqual&>(*this);
1080  }
1081 
1082 
1083  /*
1084  * Other
1085  */
1086  iterator mutable_iterator(const_iterator pos) {
1087  return iterator(const_cast<bucket_entry*>(pos.m_bucket));
1088  }
1089 
1090 private:
1091  template<class K>
1092  std::size_t hash_key(const K& key) const {
1093  return Hash::operator()(key);
1094  }
1095 
1096  template<class K1, class K2>
1097  bool compare_keys(const K1& key1, const K2& key2) const {
1098  return KeyEqual::operator()(key1, key2);
1099  }
1100 
1101  std::size_t bucket_for_hash(std::size_t hash) const {
1102  const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash);
1103  tsl_rh_assert(bucket < m_bucket_count || (bucket == 0 && m_bucket_count == 0));
1104 
1105  return bucket;
1106  }
1107 
1108  template<class U = GrowthPolicy, typename std::enable_if<is_power_of_two_policy<U>::value>::type* = nullptr>
1109  std::size_t next_bucket(std::size_t index) const noexcept {
1110  tsl_rh_assert(index < bucket_count());
1111 
1112  return (index + 1) & this->m_mask;
1113  }
1114 
1115  template<class U = GrowthPolicy, typename std::enable_if<!is_power_of_two_policy<U>::value>::type* = nullptr>
1116  std::size_t next_bucket(std::size_t index) const noexcept {
1117  tsl_rh_assert(index < bucket_count());
1118 
1119  index++;
1120  return (index != bucket_count())?index:0;
1121  }
1122 
1123 
1124 
1125  template<class K>
1126  iterator find_impl(const K& key, std::size_t hash) {
1127  return mutable_iterator(static_cast<const robin_hash*>(this)->find(key, hash));
1128  }
1129 
1130  template<class K>
1131  const_iterator find_impl(const K& key, std::size_t hash) const {
1132  std::size_t ibucket = bucket_for_hash(hash);
1133  distance_type dist_from_ideal_bucket = 0;
1134 
1135  while(dist_from_ideal_bucket <= m_buckets[ibucket].dist_from_ideal_bucket()) {
1136  if(TSL_RH_LIKELY((!USE_STORED_HASH_ON_LOOKUP || m_buckets[ibucket].bucket_hash_equal(hash)) &&
1137  compare_keys(KeySelect()(m_buckets[ibucket].value()), key)))
1138  {
1139  return const_iterator(m_buckets + ibucket);
1140  }
1141 
1142  ibucket = next_bucket(ibucket);
1143  dist_from_ideal_bucket++;
1144  }
1145 
1146  return cend();
1147  }
1148 
1149  void erase_from_bucket(iterator pos) {
1150  pos.m_bucket->clear();
1151  m_nb_elements--;
1152 
1153  /**
1154  * Backward shift, swap the empty bucket, previous_ibucket, with the values on its right, ibucket,
1155  * until we cross another empty bucket or if the other bucket has a distance_from_ideal_bucket == 0.
1156  *
1157  * We try to move the values closer to their ideal bucket.
1158  */
1159  std::size_t previous_ibucket = static_cast<std::size_t>(pos.m_bucket - m_buckets);
1160  std::size_t ibucket = next_bucket(previous_ibucket);
1161 
1162  while(m_buckets[ibucket].dist_from_ideal_bucket() > 0) {
1163  tsl_rh_assert(m_buckets[previous_ibucket].empty());
1164 
1165  const distance_type new_distance = distance_type(m_buckets[ibucket].dist_from_ideal_bucket() - 1);
1166  m_buckets[previous_ibucket].set_value_of_empty_bucket(new_distance, m_buckets[ibucket].truncated_hash(),
1167  std::move(m_buckets[ibucket].value()));
1168  m_buckets[ibucket].clear();
1169 
1170  previous_ibucket = ibucket;
1171  ibucket = next_bucket(ibucket);
1172  }
1173  }
1174 
1175  template<class K, class... Args>
1176  std::pair<iterator, bool> insert_impl(const K& key, Args&&... value_type_args) {
1177  const std::size_t hash = hash_key(key);
1178 
1179  std::size_t ibucket = bucket_for_hash(hash);
1180  distance_type dist_from_ideal_bucket = 0;
1181 
1182  while(dist_from_ideal_bucket <= m_buckets[ibucket].dist_from_ideal_bucket()) {
1183  if((!USE_STORED_HASH_ON_LOOKUP || m_buckets[ibucket].bucket_hash_equal(hash)) &&
1184  compare_keys(KeySelect()(m_buckets[ibucket].value()), key))
1185  {
1186  return std::make_pair(iterator(m_buckets + ibucket), false);
1187  }
1188 
1189  ibucket = next_bucket(ibucket);
1190  dist_from_ideal_bucket++;
1191  }
1192 
1193  if(rehash_on_extreme_load()) {
1194  ibucket = bucket_for_hash(hash);
1195  dist_from_ideal_bucket = 0;
1196 
1197  while(dist_from_ideal_bucket <= m_buckets[ibucket].dist_from_ideal_bucket()) {
1198  ibucket = next_bucket(ibucket);
1199  dist_from_ideal_bucket++;
1200  }
1201  }
1202 
1203 
1204  if(m_buckets[ibucket].empty()) {
1205  m_buckets[ibucket].set_value_of_empty_bucket(dist_from_ideal_bucket, bucket_entry::truncate_hash(hash),
1206  std::forward<Args>(value_type_args)...);
1207  }
1208  else {
1209  insert_value(ibucket, dist_from_ideal_bucket, bucket_entry::truncate_hash(hash),
1210  std::forward<Args>(value_type_args)...);
1211  }
1212 
1213 
1214  m_nb_elements++;
1215  /*
1216  * The value will be inserted in ibucket in any case, either because it was
1217  * empty or by stealing the bucket (robin hood).
1218  */
1219  return std::make_pair(iterator(m_buckets + ibucket), true);
1220  }
1221 
1222 
1223  template<class... Args>
1224  void insert_value(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1225  truncated_hash_type hash, Args&&... value_type_args)
1226  {
1227  value_type value(std::forward<Args>(value_type_args)...);
1228  insert_value_impl(ibucket, dist_from_ideal_bucket, hash, value);
1229  }
1230 
1231  void insert_value(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1232  truncated_hash_type hash, value_type&& value)
1233  {
1234  insert_value_impl(ibucket, dist_from_ideal_bucket, hash, value);
1235  }
1236 
1237  /*
1238  * We don't use `value_type&& value` as last argument due to a bug in MSVC when `value_type` is a pointer,
1239  * The compiler is not able to see the difference between `std::string*` and `std::string*&&` resulting in
1240  * compile error.
1241  *
1242  * The `value` will be in a moved state at the end of the function.
1243  */
1244  void insert_value_impl(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1245  truncated_hash_type hash, value_type& value)
1246  {
1247  m_buckets[ibucket].swap_with_value_in_bucket(dist_from_ideal_bucket, hash, value);
1248  ibucket = next_bucket(ibucket);
1249  dist_from_ideal_bucket++;
1250 
1251  while(!m_buckets[ibucket].empty()) {
1252  if(dist_from_ideal_bucket > m_buckets[ibucket].dist_from_ideal_bucket()) {
1253  if(dist_from_ideal_bucket >= REHASH_ON_HIGH_NB_PROBES__NPROBES &&
1254  load_factor() >= REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR)
1255  {
1256  /**
1257  * The number of probes is really high, rehash the map on the next insert.
1258  * Difficult to do now as rehash may throw an exception.
1259  */
1260  m_grow_on_next_insert = true;
1261  }
1262 
1263  m_buckets[ibucket].swap_with_value_in_bucket(dist_from_ideal_bucket, hash, value);
1264  }
1265 
1266  ibucket = next_bucket(ibucket);
1267  dist_from_ideal_bucket++;
1268  }
1269 
1270  m_buckets[ibucket].set_value_of_empty_bucket(dist_from_ideal_bucket, hash, std::move(value));
1271  }
1272 
1273 
1274  void rehash_impl(size_type count) {
1275  robin_hash new_table(count, static_cast<Hash&>(*this), static_cast<KeyEqual&>(*this),
1276  get_allocator(), m_min_load_factor, m_max_load_factor);
1277 
1278  const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_table.bucket_count());
1279  for(auto& bucket: m_buckets_data) {
1280  if(bucket.empty()) {
1281  continue;
1282  }
1283 
1284  const std::size_t hash = use_stored_hash?bucket.truncated_hash():
1285  new_table.hash_key(KeySelect()(bucket.value()));
1286 
1287  new_table.insert_value_on_rehash(new_table.bucket_for_hash(hash), 0,
1288  bucket_entry::truncate_hash(hash), std::move(bucket.value()));
1289  }
1290 
1291  new_table.m_nb_elements = m_nb_elements;
1292  new_table.swap(*this);
1293  }
1294 
1295  void insert_value_on_rehash(std::size_t ibucket, distance_type dist_from_ideal_bucket,
1296  truncated_hash_type hash, value_type&& value)
1297  {
1298  while(true) {
1299  if(dist_from_ideal_bucket > m_buckets[ibucket].dist_from_ideal_bucket()) {
1300  if(m_buckets[ibucket].empty()) {
1301  m_buckets[ibucket].set_value_of_empty_bucket(dist_from_ideal_bucket, hash, std::move(value));
1302  return;
1303  }
1304  else {
1305  m_buckets[ibucket].swap_with_value_in_bucket(dist_from_ideal_bucket, hash, value);
1306  }
1307  }
1308 
1309  dist_from_ideal_bucket++;
1310  ibucket = next_bucket(ibucket);
1311  }
1312  }
1313 
1314 
1315 
1316  /**
1317  * Grow the table if m_grow_on_next_insert is true or we reached the max_load_factor.
1318  * Shrink the table if m_try_skrink_on_next_insert is true (an erase occured) and
1319  * we're below the min_load_factor.
1320  *
1321  * Return true if the table has been rehashed.
1322  */
1323  bool rehash_on_extreme_load() {
1324  if(m_grow_on_next_insert || size() >= m_load_threshold) {
1325  rehash_impl(GrowthPolicy::next_bucket_count());
1326  m_grow_on_next_insert = false;
1327 
1328  return true;
1329  }
1330 
1331  if(m_try_skrink_on_next_insert) {
1332  m_try_skrink_on_next_insert = false;
1333  if(m_min_load_factor != 0.0f && load_factor() < m_min_load_factor) {
1334  reserve(size() + 1);
1335 
1336  return true;
1337  }
1338  }
1339 
1340  return false;
1341  }
1342 
1343 
1344 public:
1345  static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0;
1346 
1347  static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f;
1348  static constexpr float MINIMUM_MAX_LOAD_FACTOR = 0.2f;
1349  static constexpr float MAXIMUM_MAX_LOAD_FACTOR = 0.95f;
1350 
1351  static constexpr float DEFAULT_MIN_LOAD_FACTOR = 0.0f;
1352  static constexpr float MINIMUM_MIN_LOAD_FACTOR = 0.0f;
1353  static constexpr float MAXIMUM_MIN_LOAD_FACTOR = 0.15f;
1354 
1356  "MINIMUM_MAX_LOAD_FACTOR should be < MAXIMUM_MAX_LOAD_FACTOR");
1358  "MINIMUM_MIN_LOAD_FACTOR should be < MAXIMUM_MIN_LOAD_FACTOR");
1360  "MAXIMUM_MIN_LOAD_FACTOR should be < MINIMUM_MAX_LOAD_FACTOR");
1361 
1362 private:
1363  static const distance_type REHASH_ON_HIGH_NB_PROBES__NPROBES = 128;
1364  static constexpr float REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR = 0.15f;
1365 
1366 
1367  /**
1368  * Return an always valid pointer to an static empty bucket_entry with last_bucket() == true.
1369  */
1370  bucket_entry* static_empty_bucket_ptr() {
1371  static bucket_entry empty_bucket(true);
1372  return &empty_bucket;
1373  }
1374 
1375 private:
1376  buckets_container_type m_buckets_data;
1377 
1378  /**
1379  * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr.
1380  * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying
1381  * to find an element.
1382  *
1383  * TODO Remove m_buckets_data and only use a pointer instead of a pointer+vector to save some space in the robin_hash object.
1384  * Manage the Allocator manually.
1385  */
1386  bucket_entry* m_buckets;
1387 
1388  /**
1389  * Used a lot in find, avoid the call to m_buckets_data.size() which is a bit slower.
1390  */
1391  size_type m_bucket_count;
1392 
1393  size_type m_nb_elements;
1394 
1395  size_type m_load_threshold;
1396  float m_max_load_factor;
1397 
1398  bool m_grow_on_next_insert;
1399 
1400  float m_min_load_factor;
1401 
1402  /**
1403  * We can't shrink down the map on erase operations as the erase methods need to return the next iterator.
1404  * Shrinking the map would invalidate all the iterators and we could not return the next iterator in a meaningful way,
1405  * On erase, we thus just indicate on erase that we should try to shrink the hash table on the next insert
1406  * if we go below the min_load_factor.
1407  */
1408  bool m_try_skrink_on_next_insert;
1409 };
1410 
1411 }
1412 
1413 }
1414 
1415 #endif
std::pair< iterator, iterator > equal_range(const K &key)
Definition: robin_hash.h:999
iterator end() noexcept
Definition: robin_hash.h:675
robin_hash & operator=(robin_hash &&other)
Definition: robin_hash.h:638
friend bool operator!=(const robin_iterator &lhs, const robin_iterator &rhs)
Definition: robin_hash.h:487
Definition: robin_growth_policy.h:69
friend bool operator==(const robin_iterator &lhs, const robin_iterator &rhs)
Definition: robin_hash.h:483
size_type bucket_count() const
Definition: robin_hash.h:1024
size_type max_bucket_count() const
Definition: robin_hash.h:1028
std::pair< iterator, bool > try_emplace(K &&key, Args &&... args)
Definition: robin_hash.h:788
const_iterator find(const K &key) const
Definition: robin_hash.h:988
#define TSL_RH_LIKELY(exp)
Definition: robin_growth_policy.h:64
std::pair< iterator, bool > insert(P &&value)
Definition: robin_hash.h:718
float load_factor() const
Definition: robin_hash.h:1035
iterator find(const K &key, std::size_t hash)
Definition: robin_hash.h:982
Definition: robin_growth_policy.h:68
const robin_hash::key_type & key() const
Definition: robin_hash.h:440
U::value_type & value()
Definition: robin_hash.h:450
robin_hash(size_type bucket_count, const Hash &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:540
const T & clamp(const T &v, const T &lo, const T &hi)
Definition: robin_hash.h:72
iterator insert_hint(const_iterator hint, P &&value)
Definition: robin_hash.h:723
iterator begin() noexcept
Definition: robin_hash.h:653
iterator erase(const_iterator pos)
Definition: robin_hash.h:823
const U::value_type & at(const K &key) const
Definition: robin_hash.h:939
size_type count(const K &key) const
Definition: robin_hash.h:961
void clear() noexcept
Definition: robin_hash.h:706
void insert(InputIt first, InputIt last)
Definition: robin_hash.h:732
static constexpr float DEFAULT_MIN_LOAD_FACTOR
Definition: robin_hash.h:1351
pointer operator->() const
Definition: robin_hash.h:458
std::pair< const_iterator, const_iterator > equal_range(const K &key) const
Definition: robin_hash.h:1011
const U::value_type & at(const K &key, std::size_t hash) const
Definition: robin_hash.h:944
robin_iterator operator++(int)
Definition: robin_hash.h:476
iterator erase(iterator pos)
Definition: robin_hash.h:807
const_iterator cbegin() const noexcept
Definition: robin_hash.h:666
size_type size() const noexcept
Definition: robin_hash.h:695
robin_iterator(robin_iterator &&other)=default
robin_hash(const robin_hash &other)
Definition: robin_hash.h:573
const_iterator cend() const noexcept
Definition: robin_hash.h:683
std::pair< iterator, bool > insert_or_assign(K &&key, M &&obj)
Definition: robin_hash.h:753
void reserve(size_type count)
Definition: robin_hash.h:1067
reference operator*() const
Definition: robin_hash.h:454
iterator mutable_iterator(const_iterator pos)
Definition: robin_hash.h:1086
void min_load_factor(float ml)
Definition: robin_hash.h:1051
size_type count(const K &key, std::size_t hash) const
Definition: robin_hash.h:966
iterator emplace_hint(const_iterator hint, Args &&... args)
Definition: robin_hash.h:781
friend class robin_hash
Definition: robin_hash.h:408
size_type max_size() const noexcept
Definition: robin_hash.h:699
void swap(robin_hash &other)
Definition: robin_hash.h:906
const U::value_type & value() const
Definition: robin_hash.h:445
std::pair< const_iterator, const_iterator > equal_range(const K &key, std::size_t hash) const
Definition: robin_hash.h:1016
static constexpr float MINIMUM_MIN_LOAD_FACTOR
Definition: robin_hash.h:1352
const_iterator begin() const noexcept
Definition: robin_hash.h:662
void max_load_factor(float ml)
Definition: robin_hash.h:1056
#define tsl_rh_assert(expr)
Definition: robin_growth_policy.h:42
iterator erase(const_iterator first, const_iterator last)
Definition: robin_hash.h:827
const_iterator find(const K &key, std::size_t hash) const
Definition: robin_hash.h:993
U::value_type & at(const K &key)
Definition: robin_hash.h:928
iterator find(const K &key)
Definition: robin_hash.h:977
robin_hash & operator=(const robin_hash &other)
Definition: robin_hash.h:615
static constexpr float MINIMUM_MAX_LOAD_FACTOR
Definition: robin_hash.h:1348
robin_iterator(const robin_iterator<!TIsConst > &other) noexcept
Definition: robin_hash.h:432
static constexpr float MAXIMUM_MAX_LOAD_FACTOR
Definition: robin_hash.h:1349
std::pair< iterator, iterator > equal_range(const K &key, std::size_t hash)
Definition: robin_hash.h:1004
#define TSL_RH_THROW_OR_TERMINATE(ex, msg)
Definition: robin_growth_policy.h:56
key_equal key_eq() const
Definition: robin_hash.h:1078
U::value_type & operator[](K &&key)
Definition: robin_hash.h:955
robin_iterator & operator=(const robin_iterator &other)=default
robin_iterator() noexcept
Definition: robin_hash.h:427
static const size_type DEFAULT_INIT_BUCKETS_SIZE
Definition: robin_hash.h:1345
static constexpr float MAXIMUM_MIN_LOAD_FACTOR
Definition: robin_hash.h:1353
static constexpr float DEFAULT_MAX_LOAD_FACTOR
Definition: robin_hash.h:1347
hasher hash_function() const
Definition: robin_hash.h:1074
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:588
std::pair< iterator, bool > emplace(Args &&... args)
Definition: robin_hash.h:776
Definition: robin_hash.h:317
iterator insert_or_assign(const_iterator hint, K &&key, M &&obj)
Definition: robin_hash.h:763
size_type erase(const K &key)
Definition: robin_hash.h:884
iterator try_emplace_hint(const_iterator hint, K &&key, Args &&... args)
Definition: robin_hash.h:795
void rehash(size_type count)
Definition: robin_hash.h:1062
robin_iterator & operator=(robin_iterator &&other)=default
float max_load_factor() const
Definition: robin_hash.h:1047
const_iterator end() const noexcept
Definition: robin_hash.h:679
Definition: robin_growth_policy.h:78
robin_iterator & operator++()
Definition: robin_hash.h:462
robin_iterator(const robin_iterator &other)=default
float min_load_factor() const
Definition: robin_hash.h:1043
bool empty() const noexcept
Definition: robin_hash.h:691
allocator_type get_allocator() const
Definition: robin_hash.h:645
Definition: robin_hash.h:47
size_type erase(const K &key, std::size_t hash)
Definition: robin_hash.h:889
U::value_type & at(const K &key, std::size_t hash)
Definition: robin_hash.h:933