1 /*
2     Copyright (c) 2005-2022 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #ifndef __TBB_detail__concurrent_unordered_base_H
18 #define __TBB_detail__concurrent_unordered_base_H
19 
20 #if !defined(__TBB_concurrent_unordered_map_H) && !defined(__TBB_concurrent_unordered_set_H)
21 #error Do not #include this internal file directly; use public TBB headers instead.
22 #endif
23 
24 #include "_range_common.h"
25 #include "_containers_helpers.h"
26 #include "_segment_table.h"
27 #include "_hash_compare.h"
28 #include "_allocator_traits.h"
29 #include "_node_handle.h"
30 #include "_assert.h"
31 #include "_utils.h"
32 #include "_exception.h"
33 #include <iterator>
34 #include <utility>
35 #include <functional>
36 #include <initializer_list>
37 #include <atomic>
38 #include <type_traits>
39 #include <memory>
40 #include <algorithm>
41 
42 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
43 #pragma warning(push)
44 #pragma warning(disable: 4127) // warning C4127: conditional expression is constant
45 #endif
46 
47 namespace tbb {
48 namespace detail {
49 namespace d1 {
50 
51 template <typename Traits>
52 class concurrent_unordered_base;
53 
54 template<typename Container, typename Value>
55 class solist_iterator {
56 private:
57     using node_ptr = typename Container::value_node_ptr;
58     template <typename T, typename Allocator>
59     friend class split_ordered_list;
60     template<typename M, typename V>
61     friend class solist_iterator;
62     template <typename Traits>
63     friend class concurrent_unordered_base;
64     template<typename M, typename T, typename U>
65     friend bool operator==( const solist_iterator<M,T>& i, const solist_iterator<M,U>& j );
66     template<typename M, typename T, typename U>
67     friend bool operator!=( const solist_iterator<M,T>& i, const solist_iterator<M,U>& j );
68 public:
69     using value_type = Value;
70     using difference_type = typename Container::difference_type;
71     using pointer = value_type*;
72     using reference = value_type&;
73     using iterator_category = std::forward_iterator_tag;
74 
75     solist_iterator() : my_node_ptr(nullptr) {}
76     solist_iterator( const solist_iterator<Container, typename Container::value_type>& other )
77         : my_node_ptr(other.my_node_ptr) {}
78 
79     solist_iterator& operator=( const solist_iterator<Container, typename Container::value_type>& other ) {
80         my_node_ptr = other.my_node_ptr;
81         return *this;
82     }
83 
84     reference operator*() const {
85         return my_node_ptr->value();
86     }
87 
88     pointer operator->() const {
89         return my_node_ptr->storage();
90     }
91 
92     solist_iterator& operator++() {
93         auto next_node = my_node_ptr->next();
94         while(next_node && next_node->is_dummy()) {
95             next_node = next_node->next();
96         }
97         my_node_ptr = static_cast<node_ptr>(next_node);
98         return *this;
99     }
100 
101     solist_iterator operator++(int) {
102         solist_iterator tmp = *this;
103         ++*this;
104         return tmp;
105     }
106 
107 private:
108     solist_iterator( node_ptr pnode ) : my_node_ptr(pnode) {}
109 
110     node_ptr get_node_ptr() const { return my_node_ptr; }
111 
112     node_ptr my_node_ptr;
113 };
114 
115 template<typename Solist, typename T, typename U>
116 bool operator==( const solist_iterator<Solist, T>& i, const solist_iterator<Solist, U>& j ) {
117     return i.my_node_ptr == j.my_node_ptr;
118 }
119 
120 template<typename Solist, typename T, typename U>
121 bool operator!=( const solist_iterator<Solist, T>& i, const solist_iterator<Solist, U>& j ) {
122     return i.my_node_ptr != j.my_node_ptr;
123 }
124 
125 template <typename SokeyType>
126 class list_node {
127 public:
128     using node_ptr = list_node*;
129     using sokey_type = SokeyType;
130 
131     list_node(sokey_type key) : my_next(nullptr), my_order_key(key) {}
132 
133     void init( sokey_type key ) {
134         my_order_key = key;
135     }
136 
137     sokey_type order_key() const {
138         return my_order_key;
139     }
140 
141     bool is_dummy() {
142         // The last bit of order key is unset for dummy nodes
143         return (my_order_key & 0x1) == 0;
144     }
145 
146     node_ptr next() const {
147         return my_next.load(std::memory_order_acquire);
148     }
149 
150     void set_next( node_ptr next_node ) {
151         my_next.store(next_node, std::memory_order_release);
152     }
153 
154     bool try_set_next( node_ptr expected_next, node_ptr new_next ) {
155         return my_next.compare_exchange_strong(expected_next, new_next);
156     }
157 
158 private:
159     std::atomic<node_ptr> my_next;
160     sokey_type my_order_key;
161 }; // class list_node
162 
163 template <typename ValueType, typename SokeyType>
164 class value_node : public list_node<SokeyType>
165 {
166 public:
167     using base_type = list_node<SokeyType>;
168     using sokey_type = typename base_type::sokey_type;
169     using value_type = ValueType;
170 
171     value_node( sokey_type ord_key ) : base_type(ord_key) {}
172     ~value_node() {}
173     value_type* storage() {
174         return reinterpret_cast<value_type*>(&my_value);
175     }
176 
177     value_type& value() {
178         return *storage();
179     }
180 
181 private:
182     using aligned_storage_type = typename std::aligned_storage<sizeof(value_type)>::type;
183     aligned_storage_type my_value;
184 }; // class value_node
185 
186 template <typename Traits>
187 class concurrent_unordered_base {
188     using self_type = concurrent_unordered_base<Traits>;
189     using traits_type = Traits;
190     using hash_compare_type = typename traits_type::hash_compare_type;
191     class unordered_segment_table;
192 public:
193     using value_type = typename traits_type::value_type;
194     using key_type = typename traits_type::key_type;
195     using allocator_type = typename traits_type::allocator_type;
196 
197 private:
198     using allocator_traits_type = tbb::detail::allocator_traits<allocator_type>;
199     // TODO: check assert conditions for different C++ standards
200     static_assert(std::is_same<typename allocator_traits_type::value_type, value_type>::value,
201                   "value_type of the container must be the same as its allocator");
202     using sokey_type = std::size_t;
203 
204 public:
205     using size_type = std::size_t;
206     using difference_type = std::ptrdiff_t;
207 
208     using iterator = solist_iterator<self_type, value_type>;
209     using const_iterator = solist_iterator<self_type, const value_type>;
210     using local_iterator = iterator;
211     using const_local_iterator = const_iterator;
212 
213     using reference = value_type&;
214     using const_reference = const value_type&;
215     using pointer = typename allocator_traits_type::pointer;
216     using const_pointer = typename allocator_traits_type::const_pointer;
217 
218     using hasher = typename hash_compare_type::hasher;
219     using key_equal = typename hash_compare_type::key_equal;
220 
221 private:
222     using list_node_type = list_node<sokey_type>;
223     using value_node_type = value_node<value_type, sokey_type>;
224     using node_ptr = list_node_type*;
225     using value_node_ptr = value_node_type*;
226 
227     using value_node_allocator_type = typename allocator_traits_type::template rebind_alloc<value_node_type>;
228     using node_allocator_type = typename allocator_traits_type::template rebind_alloc<list_node_type>;
229 
230     using node_allocator_traits = tbb::detail::allocator_traits<node_allocator_type>;
231     using value_node_allocator_traits = tbb::detail::allocator_traits<value_node_allocator_type>;
232 
233     static constexpr size_type round_up_to_power_of_two( size_type bucket_count ) {
234         return size_type(1) << size_type(tbb::detail::log2(uintptr_t(bucket_count == 0 ? 1 : bucket_count) * 2 - 1));
235     }
236 
237     template <typename T>
238     using is_transparent = dependent_bool<has_transparent_key_equal<key_type, hasher, key_equal>, T>;
239 public:
240     using node_type = node_handle<key_type, value_type, value_node_type, allocator_type>;
241 
242     explicit concurrent_unordered_base( size_type bucket_count, const hasher& hash = hasher(),
243                                         const key_equal& equal = key_equal(), const allocator_type& alloc = allocator_type() )
244         : my_size(0),
245           my_bucket_count(round_up_to_power_of_two(bucket_count)),
246           my_max_load_factor(float(initial_max_load_factor)),
247           my_hash_compare(hash, equal),
248           my_head(sokey_type(0)),
249           my_segments(alloc) {}
250 
251     concurrent_unordered_base() : concurrent_unordered_base(initial_bucket_count) {}
252 
253     concurrent_unordered_base( size_type bucket_count, const allocator_type& alloc )
254         : concurrent_unordered_base(bucket_count, hasher(), key_equal(), alloc) {}
255 
256     concurrent_unordered_base( size_type bucket_count, const hasher& hash, const allocator_type& alloc )
257         : concurrent_unordered_base(bucket_count, hash, key_equal(), alloc) {}
258 
259     explicit concurrent_unordered_base( const allocator_type& alloc )
260         : concurrent_unordered_base(initial_bucket_count, hasher(), key_equal(), alloc) {}
261 
262     template <typename InputIterator>
263     concurrent_unordered_base( InputIterator first, InputIterator last,
264                                size_type bucket_count = initial_bucket_count, const hasher& hash = hasher(),
265                                const key_equal& equal = key_equal(), const allocator_type& alloc = allocator_type() )
266         : concurrent_unordered_base(bucket_count, hash, equal, alloc)
267     {
268         insert(first, last);
269     }
270 
271     template <typename InputIterator>
272     concurrent_unordered_base( InputIterator first, InputIterator last,
273                                size_type bucket_count, const allocator_type& alloc )
274         : concurrent_unordered_base(first, last, bucket_count, hasher(), key_equal(), alloc) {}
275 
276     template <typename InputIterator>
277     concurrent_unordered_base( InputIterator first, InputIterator last,
278                                size_type bucket_count, const hasher& hash, const allocator_type& alloc )
279         : concurrent_unordered_base(first, last, bucket_count, hash, key_equal(), alloc) {}
280 
281     concurrent_unordered_base( const concurrent_unordered_base& other )
282         : my_size(other.my_size.load(std::memory_order_relaxed)),
283           my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)),
284           my_max_load_factor(other.my_max_load_factor),
285           my_hash_compare(other.my_hash_compare),
286           my_head(other.my_head.order_key()),
287           my_segments(other.my_segments)
288     {
289         try_call( [&] {
290             internal_copy(other);
291         } ).on_exception( [&] {
292             clear();
293         });
294     }
295 
296     concurrent_unordered_base( const concurrent_unordered_base& other, const allocator_type& alloc )
297         : my_size(other.my_size.load(std::memory_order_relaxed)),
298           my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)),
299           my_max_load_factor(other.my_max_load_factor),
300           my_hash_compare(other.my_hash_compare),
301           my_head(other.my_head.order_key()),
302           my_segments(other.my_segments, alloc)
303     {
304         try_call( [&] {
305             internal_copy(other);
306         } ).on_exception( [&] {
307             clear();
308         });
309     }
310 
311     concurrent_unordered_base( concurrent_unordered_base&& other )
312         : my_size(other.my_size.load(std::memory_order_relaxed)),
313           my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)),
314           my_max_load_factor(std::move(other.my_max_load_factor)),
315           my_hash_compare(std::move(other.my_hash_compare)),
316           my_head(other.my_head.order_key()),
317           my_segments(std::move(other.my_segments))
318     {
319         move_content(std::move(other));
320     }
321 
322     concurrent_unordered_base( concurrent_unordered_base&& other, const allocator_type& alloc )
323         : my_size(other.my_size.load(std::memory_order_relaxed)),
324           my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)),
325           my_max_load_factor(std::move(other.my_max_load_factor)),
326           my_hash_compare(std::move(other.my_hash_compare)),
327           my_head(other.my_head.order_key()),
328           my_segments(std::move(other.my_segments), alloc)
329     {
330         using is_always_equal = typename allocator_traits_type::is_always_equal;
331         internal_move_construct_with_allocator(std::move(other), alloc, is_always_equal());
332     }
333 
334     concurrent_unordered_base( std::initializer_list<value_type> init,
335                                size_type bucket_count = initial_bucket_count,
336                                const hasher& hash = hasher(), const key_equal& equal = key_equal(),
337                                const allocator_type& alloc = allocator_type() )
338         : concurrent_unordered_base(init.begin(), init.end(), bucket_count, hash, equal, alloc) {}
339 
340     concurrent_unordered_base( std::initializer_list<value_type> init,
341                                size_type bucket_count, const allocator_type& alloc )
342         : concurrent_unordered_base(init, bucket_count, hasher(), key_equal(), alloc) {}
343 
344     concurrent_unordered_base( std::initializer_list<value_type> init,
345                                size_type bucket_count, const hasher& hash, const allocator_type& alloc )
346         : concurrent_unordered_base(init, bucket_count, hash, key_equal(), alloc) {}
347 
348     ~concurrent_unordered_base() {
349         internal_clear();
350     }
351 
352     concurrent_unordered_base& operator=( const concurrent_unordered_base& other ) {
353         if (this != &other) {
354             clear();
355             my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
356             my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed);
357             my_max_load_factor = other.my_max_load_factor;
358             my_hash_compare = other.my_hash_compare;
359             my_segments = other.my_segments;
360             internal_copy(other); // TODO: guards for exceptions?
361         }
362         return *this;
363     }
364 
365     concurrent_unordered_base& operator=( concurrent_unordered_base&& other ) noexcept(unordered_segment_table::is_noexcept_assignment) {
366         if (this != &other) {
367             clear();
368             my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
369             my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed);
370             my_max_load_factor = std::move(other.my_max_load_factor);
371             my_hash_compare = std::move(other.my_hash_compare);
372             my_segments = std::move(other.my_segments);
373 
374             using pocma_type = typename allocator_traits_type::propagate_on_container_move_assignment;
375             using is_always_equal = typename allocator_traits_type::is_always_equal;
376             internal_move_assign(std::move(other), tbb::detail::disjunction<pocma_type, is_always_equal>());
377         }
378         return *this;
379     }
380 
381     concurrent_unordered_base& operator=( std::initializer_list<value_type> init ) {
382         clear();
383         insert(init);
384         return *this;
385     }
386 
387     void swap( concurrent_unordered_base& other ) noexcept(unordered_segment_table::is_noexcept_swap) {
388         if (this != &other) {
389             using pocs_type = typename allocator_traits_type::propagate_on_container_swap;
390             using is_always_equal = typename allocator_traits_type::is_always_equal;
391             internal_swap(other, tbb::detail::disjunction<pocs_type, is_always_equal>());
392         }
393     }
394 
395     allocator_type get_allocator() const noexcept { return my_segments.get_allocator(); }
396 
397     iterator begin() noexcept { return iterator(first_value_node(&my_head)); }
398     const_iterator begin() const noexcept { return const_iterator(first_value_node(const_cast<node_ptr>(&my_head))); }
399     const_iterator cbegin() const noexcept { return const_iterator(first_value_node(const_cast<node_ptr>(&my_head))); }
400 
401     iterator end() noexcept { return iterator(nullptr); }
402     const_iterator end() const noexcept { return const_iterator(nullptr); }
403     const_iterator cend() const noexcept { return const_iterator(nullptr); }
404 
405     __TBB_nodiscard bool empty() const noexcept { return size() == 0; }
406     size_type size() const noexcept { return my_size.load(std::memory_order_relaxed); }
407     size_type max_size() const noexcept { return allocator_traits_type::max_size(get_allocator()); }
408 
409     void clear() noexcept {
410         internal_clear();
411     }
412 
413     std::pair<iterator, bool> insert( const value_type& value ) {
414         return internal_insert_value(value);
415     }
416 
417     std::pair<iterator, bool> insert( value_type&& value ) {
418         return internal_insert_value(std::move(value));
419     }
420 
421     iterator insert( const_iterator, const value_type& value ) {
422         // Ignore hint
423         return insert(value).first;
424     }
425 
426     iterator insert( const_iterator, value_type&& value ) {
427         // Ignore hint
428         return insert(std::move(value)).first;
429     }
430 
431     template <typename InputIterator>
432     void insert( InputIterator first, InputIterator last ) {
433         for (; first != last; ++first) {
434             insert(*first);
435         }
436     }
437 
438     void insert( std::initializer_list<value_type> init ) {
439         insert(init.begin(), init.end());
440     }
441 
442     std::pair<iterator, bool> insert( node_type&& nh ) {
443         if (!nh.empty()) {
444             value_node_ptr insert_node = node_handle_accessor::get_node_ptr(nh);
445             auto init_node = [&insert_node]( sokey_type order_key )->value_node_ptr {
446                 insert_node->init(order_key);
447                 return insert_node;
448             };
449             auto insert_result = internal_insert(insert_node->value(), init_node);
450             if (insert_result.inserted) {
451                 // If the insertion succeeded - set node handle to the empty state
452                 __TBB_ASSERT(insert_result.remaining_node == nullptr,
453                             "internal_insert_node should not return the remaining node if the insertion succeeded");
454                 node_handle_accessor::deactivate(nh);
455             }
456             return { iterator(insert_result.node_with_equal_key), insert_result.inserted };
457         }
458         return {end(), false};
459     }
460 
461     iterator insert( const_iterator, node_type&& nh ) {
462         // Ignore hint
463         return insert(std::move(nh)).first;
464     }
465 
466     template <typename... Args>
467     std::pair<iterator, bool> emplace( Args&&... args ) {
468         // Create a node with temporary order_key 0, which will be reinitialize
469         // in internal_insert after the hash calculation
470         value_node_ptr insert_node = create_node(0, std::forward<Args>(args)...);
471 
472         auto init_node = [&insert_node]( sokey_type order_key )->value_node_ptr {
473             insert_node->init(order_key);
474             return insert_node;
475         };
476 
477         auto insert_result = internal_insert(insert_node->value(), init_node);
478 
479         if (!insert_result.inserted) {
480             // If the insertion failed - destroy the node which was created
481             insert_node->init(split_order_key_regular(1));
482             destroy_node(insert_node);
483         }
484 
485         return { iterator(insert_result.node_with_equal_key), insert_result.inserted };
486     }
487 
488     template <typename... Args>
489     iterator emplace_hint( const_iterator, Args&&... args ) {
490         // Ignore hint
491         return emplace(std::forward<Args>(args)...).first;
492     }
493 
494     iterator unsafe_erase( const_iterator pos ) {
495         return iterator(first_value_node(internal_erase(pos.get_node_ptr())));
496     }
497 
498     iterator unsafe_erase( iterator pos ) {
499         return iterator(first_value_node(internal_erase(pos.get_node_ptr())));
500     }
501 
502     iterator unsafe_erase( const_iterator first, const_iterator last ) {
503         while(first != last) {
504             first = unsafe_erase(first);
505         }
506         return iterator(first.get_node_ptr());
507     }
508 
509     size_type unsafe_erase( const key_type& key ) {
510         return internal_erase_by_key(key);
511     }
512 
513     template <typename K>
514     typename std::enable_if<is_transparent<K>::value
515                             && !std::is_convertible<K, const_iterator>::value
516                             && !std::is_convertible<K, iterator>::value,
517                             size_type>::type unsafe_erase( const K& key )
518     {
519         return internal_erase_by_key(key);
520     }
521 
522     node_type unsafe_extract( const_iterator pos ) {
523         internal_extract(pos.get_node_ptr());
524         return node_handle_accessor::construct<node_type>(pos.get_node_ptr());
525     }
526 
527     node_type unsafe_extract( iterator pos ) {
528         internal_extract(pos.get_node_ptr());
529         return node_handle_accessor::construct<node_type>(pos.get_node_ptr());
530     }
531 
532     node_type unsafe_extract( const key_type& key ) {
533         iterator item = find(key);
534         return item == end() ? node_type() : unsafe_extract(item);
535     }
536 
537     template <typename K>
538     typename std::enable_if<is_transparent<K>::value
539                             && !std::is_convertible<K, const_iterator>::value
540                             && !std::is_convertible<K, iterator>::value,
541                             node_type>::type unsafe_extract( const K& key )
542     {
543         iterator item = find(key);
544         return item == end() ? node_type() : unsafe_extract(item);
545     }
546 
547     // Lookup functions
548     iterator find( const key_type& key ) {
549         value_node_ptr result = internal_find(key);
550         return result == nullptr ? end() : iterator(result);
551     }
552 
553     const_iterator find( const key_type& key ) const {
554         value_node_ptr result = const_cast<self_type*>(this)->internal_find(key);
555         return result == nullptr ? end() : const_iterator(result);
556     }
557 
558     template <typename K>
559     typename std::enable_if<is_transparent<K>::value, iterator>::type find( const K& key ) {
560         value_node_ptr result = internal_find(key);
561         return result == nullptr ? end() : iterator(result);
562     }
563 
564     template <typename K>
565     typename std::enable_if<is_transparent<K>::value, const_iterator>::type find( const K& key ) const {
566         value_node_ptr result = const_cast<self_type*>(this)->internal_find(key);
567         return result == nullptr ? end() : const_iterator(result);
568     }
569 
570     std::pair<iterator, iterator> equal_range( const key_type& key ) {
571         auto result = internal_equal_range(key);
572         return std::make_pair(iterator(result.first), iterator(result.second));
573     }
574 
575     std::pair<const_iterator, const_iterator> equal_range( const key_type& key ) const {
576         auto result = const_cast<self_type*>(this)->internal_equal_range(key);
577         return std::make_pair(const_iterator(result.first), const_iterator(result.second));
578     }
579 
580     template <typename K>
581     typename std::enable_if<is_transparent<K>::value, std::pair<iterator, iterator>>::type equal_range( const K& key ) {
582         auto result = internal_equal_range(key);
583         return std::make_pair(iterator(result.first), iterator(result.second));
584     }
585 
586     template <typename K>
587     typename std::enable_if<is_transparent<K>::value, std::pair<const_iterator, const_iterator>>::type equal_range( const K& key ) const {
588         auto result = const_cast<self_type*>(this)->internal_equal_range(key);
589         return std::make_pair(iterator(result.first), iterator(result.second));
590     }
591 
592     size_type count( const key_type& key ) const {
593         return internal_count(key);
594     }
595 
596     template <typename K>
597     typename std::enable_if<is_transparent<K>::value, size_type>::type count( const K& key ) const {
598         return internal_count(key);
599     }
600 
601     bool contains( const key_type& key ) const {
602         return find(key) != end();
603     }
604 
605     template <typename K>
606     typename std::enable_if<is_transparent<K>::value, bool>::type contains( const K& key ) const {
607         return find(key) != end();
608     }
609 
610     // Bucket interface
611     local_iterator unsafe_begin( size_type n ) {
612         return local_iterator(first_value_node(get_bucket(n)));
613     }
614 
615     const_local_iterator unsafe_begin( size_type n ) const {
616         auto bucket_begin = first_value_node(const_cast<self_type*>(this)->get_bucket(n));
617         return const_local_iterator(bucket_begin);
618     }
619 
620     const_local_iterator unsafe_cbegin( size_type n ) const {
621         auto bucket_begin = first_value_node(const_cast<self_type*>(this)->get_bucket(n));
622         return const_local_iterator(bucket_begin);
623     }
624 
625     local_iterator unsafe_end( size_type n ) {
626         size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed);
627         return n != bucket_count - 1 ? unsafe_begin(get_next_bucket_index(n)) : local_iterator(nullptr);
628     }
629 
630     const_local_iterator unsafe_end( size_type n ) const {
631         size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed);
632         return n != bucket_count - 1 ? unsafe_begin(get_next_bucket_index(n)) : const_local_iterator(nullptr);
633     }
634 
635     const_local_iterator unsafe_cend( size_type n ) const {
636         size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed);
637         return n != bucket_count - 1 ? unsafe_begin(get_next_bucket_index(n)) : const_local_iterator(nullptr);
638     }
639 
640     size_type unsafe_bucket_count() const { return my_bucket_count.load(std::memory_order_relaxed); }
641 
642     size_type unsafe_max_bucket_count() const {
643         return max_size();
644     }
645 
646     size_type unsafe_bucket_size( size_type n ) const {
647         return size_type(std::distance(unsafe_begin(n), unsafe_end(n)));
648     }
649 
650     size_type unsafe_bucket( const key_type& key ) const {
651         return my_hash_compare(key) % my_bucket_count.load(std::memory_order_relaxed);
652     }
653 
654     // Hash policy
655     float load_factor() const {
656         return float(size() / float(my_bucket_count.load(std::memory_order_acquire)));
657     }
658 
659     float max_load_factor() const { return my_max_load_factor; }
660 
661     void max_load_factor( float mlf ) {
662         if (mlf != mlf || mlf < 0) {
663             tbb::detail::throw_exception(exception_id::invalid_load_factor);
664         }
665         my_max_load_factor = mlf;
666     } // TODO: unsafe?
667 
668     void rehash( size_type bucket_count ) {
669         size_type current_bucket_count = my_bucket_count.load(std::memory_order_acquire);
670         if (current_bucket_count < bucket_count) {
671             // TODO: do we need do-while here?
672             my_bucket_count.compare_exchange_strong(current_bucket_count, round_up_to_power_of_two(bucket_count));
673         }
674     }
675 
676     void reserve( size_type elements_count ) {
677         size_type current_bucket_count = my_bucket_count.load(std::memory_order_acquire);
678         size_type necessary_bucket_count = current_bucket_count;
679 
680         do {
681             // TODO: Log2 seems useful here
682             while (necessary_bucket_count * max_load_factor() < elements_count) {
683                 necessary_bucket_count <<= 1;
684             }
685         } while (current_bucket_count >= necessary_bucket_count ||
686                  !my_bucket_count.compare_exchange_strong(current_bucket_count, necessary_bucket_count));
687     }
688 
689     // Observers
690     hasher hash_function() const { return my_hash_compare.hash_function(); }
691     key_equal key_eq() const { return my_hash_compare.key_eq(); }
692 
693     class const_range_type {
694     private:
695         const concurrent_unordered_base& my_instance;
696         node_ptr my_begin_node; // may be node* const
697         node_ptr my_end_node;
698         mutable node_ptr my_midpoint_node;
699     public:
700         using size_type = typename concurrent_unordered_base::size_type;
701         using value_type = typename concurrent_unordered_base::value_type;
702         using reference = typename concurrent_unordered_base::reference;
703         using difference_type = typename concurrent_unordered_base::difference_type;
704         using iterator = typename concurrent_unordered_base::const_iterator;
705 
706         bool empty() const { return my_begin_node == my_end_node; }
707 
708         bool is_divisible() const {
709             return my_midpoint_node != my_end_node;
710         }
711 
712         size_type grainsize() const { return 1; }
713 
714         const_range_type( const_range_type& range, split )
715             : my_instance(range.my_instance),
716               my_begin_node(range.my_midpoint_node),
717               my_end_node(range.my_end_node)
718         {
719             range.my_end_node = my_begin_node;
720             __TBB_ASSERT(!empty(), "Splitting despite the range is not divisible");
721             __TBB_ASSERT(!range.empty(), "Splitting despite the range is not divisible");
722             set_midpoint();
723             range.set_midpoint();
724         }
725 
726         iterator begin() const { return iterator(my_instance.first_value_node(my_begin_node)); }
727         iterator end() const { return iterator(my_instance.first_value_node(my_end_node)); }
728 
729         const_range_type( const concurrent_unordered_base& table )
730             : my_instance(table), my_begin_node(my_instance.first_value_node(const_cast<node_ptr>(&table.my_head))), my_end_node(nullptr)
731         {
732             set_midpoint();
733         }
734     private:
735         void set_midpoint() const {
736             if (empty()) {
737                 my_midpoint_node = my_end_node;
738             } else {
739                 sokey_type invalid_key = ~sokey_type(0);
740                 sokey_type begin_key = my_begin_node != nullptr ? my_begin_node->order_key() : invalid_key;
741                 sokey_type end_key = my_end_node != nullptr ? my_end_node->order_key() : invalid_key;
742 
743                 size_type mid_bucket = reverse_bits(begin_key + (end_key - begin_key) / 2) %
744                     my_instance.my_bucket_count.load(std::memory_order_relaxed);
745                 while( my_instance.my_segments[mid_bucket].load(std::memory_order_relaxed) == nullptr) {
746                     mid_bucket = my_instance.get_parent(mid_bucket);
747                 }
748                 if (reverse_bits(mid_bucket) > begin_key) {
749                     // Found a dummy node between begin and end
750                     my_midpoint_node = my_instance.first_value_node(
751                         my_instance.my_segments[mid_bucket].load(std::memory_order_relaxed));
752                 } else {
753                     // Didn't find a dummy node between begin and end
754                     my_midpoint_node = my_end_node;
755                 }
756             }
757         }
758     }; // class const_range_type
759 
760     class range_type : public const_range_type {
761     public:
762         using iterator = typename concurrent_unordered_base::iterator;
763         using const_range_type::const_range_type;
764 
765         iterator begin() const { return iterator(const_range_type::begin().get_node_ptr()); }
766         iterator end() const { return iterator(const_range_type::end().get_node_ptr()); }
767     }; // class range_type
768 
769     // Parallel iteration
770     range_type range() {
771         return range_type(*this);
772     }
773 
774     const_range_type range() const {
775         return const_range_type(*this);
776     }
777 protected:
778     static constexpr bool allow_multimapping = traits_type::allow_multimapping;
779 
780 private:
781     static constexpr size_type initial_bucket_count = 8;
782     static constexpr float initial_max_load_factor = 4; // TODO: consider 1?
783     static constexpr size_type pointers_per_embedded_table = sizeof(size_type) * 8 - 1;
784 
785     class unordered_segment_table
786         : public segment_table<std::atomic<node_ptr>, allocator_type, unordered_segment_table, pointers_per_embedded_table>
787     {
788         using self_type = unordered_segment_table;
789         using atomic_node_ptr = std::atomic<node_ptr>;
790         using base_type = segment_table<std::atomic<node_ptr>, allocator_type, unordered_segment_table, pointers_per_embedded_table>;
791         using segment_type = typename base_type::segment_type;
792         using base_allocator_type = typename base_type::allocator_type;
793 
794         using segment_allocator_type = typename allocator_traits_type::template rebind_alloc<atomic_node_ptr>;
795         using segment_allocator_traits = tbb::detail::allocator_traits<segment_allocator_type>;
796     public:
797         // Segment table for unordered containers should not be extended in the wait- free implementation
798         static constexpr bool allow_table_extending = false;
799         static constexpr bool is_noexcept_assignment = std::is_nothrow_move_assignable<hasher>::value &&
800                                                        std::is_nothrow_move_assignable<key_equal>::value &&
801                                                        segment_allocator_traits::is_always_equal::value;
802         static constexpr bool is_noexcept_swap = tbb::detail::is_nothrow_swappable<hasher>::value &&
803                                                  tbb::detail::is_nothrow_swappable<key_equal>::value &&
804                                                  segment_allocator_traits::is_always_equal::value;
805 
806         // TODO: using base_type::base_type is not compiling on Windows and Intel Compiler - investigate
807         unordered_segment_table( const base_allocator_type& alloc = base_allocator_type() )
808             : base_type(alloc) {}
809 
810         unordered_segment_table( const unordered_segment_table& ) = default;
811 
812         unordered_segment_table( const unordered_segment_table& other, const base_allocator_type& alloc )
813             : base_type(other, alloc) {}
814 
815         unordered_segment_table( unordered_segment_table&& ) = default;
816 
817         unordered_segment_table( unordered_segment_table&& other, const base_allocator_type& alloc )
818             : base_type(std::move(other), alloc) {}
819 
820         unordered_segment_table& operator=( const unordered_segment_table& ) = default;
821 
822         unordered_segment_table& operator=( unordered_segment_table&& ) = default;
823 
824         segment_type create_segment( typename base_type::segment_table_type, typename base_type::segment_index_type segment_index, size_type ) {
825             segment_allocator_type alloc(this->get_allocator());
826             size_type seg_size = this->segment_size(segment_index);
827             segment_type new_segment = segment_allocator_traits::allocate(alloc, seg_size);
828             for (size_type i = 0; i != seg_size; ++i) {
829                 segment_allocator_traits::construct(alloc, new_segment + i, nullptr);
830             }
831             return new_segment;
832         }
833 
834         segment_type nullify_segment( typename base_type::segment_table_type table, size_type segment_index ) {
835             segment_type target_segment = table[segment_index].load(std::memory_order_relaxed);
836             table[segment_index].store(nullptr, std::memory_order_relaxed);
837             return target_segment;
838         }
839 
840         // deallocate_segment is required by the segment_table base class, but
841         // in unordered, it is also necessary to call the destructor during deallocation
842         void deallocate_segment( segment_type address, size_type index ) {
843             destroy_segment(address, index);
844         }
845 
846         void destroy_segment( segment_type address, size_type index ) {
847             segment_allocator_type alloc(this->get_allocator());
848             for (size_type i = 0; i != this->segment_size(index); ++i) {
849                 segment_allocator_traits::destroy(alloc, address + i);
850             }
851             segment_allocator_traits::deallocate(alloc, address, this->segment_size(index));
852         }
853 
854 
855         void copy_segment( size_type index, segment_type, segment_type to ) {
856             if (index == 0) {
857                 // The first element in the first segment is embedded into the table (my_head)
858                 // so the first pointer should not be stored here
859                 // It would be stored during move ctor/assignment operation
860                 to[1].store(nullptr, std::memory_order_relaxed);
861             } else {
862                 for (size_type i = 0; i != this->segment_size(index); ++i) {
863                     to[i].store(nullptr, std::memory_order_relaxed);
864                 }
865             }
866         }
867 
868         void move_segment( size_type index, segment_type from, segment_type to ) {
869             if (index == 0) {
870                 // The first element in the first segment is embedded into the table (my_head)
871                 // so the first pointer should not be stored here
872                 // It would be stored during move ctor/assignment operation
873                 to[1].store(from[1].load(std::memory_order_relaxed), std::memory_order_relaxed);
874             } else {
875                 for (size_type i = 0; i != this->segment_size(index); ++i) {
876                     to[i].store(from[i].load(std::memory_order_relaxed), std::memory_order_relaxed);
877                     from[i].store(nullptr, std::memory_order_relaxed);
878                 }
879             }
880         }
881 
882         // allocate_long_table is required by the segment_table base class, but unused for unordered containers
883         typename base_type::segment_table_type allocate_long_table( const typename base_type::atomic_segment*, size_type ) {
884             __TBB_ASSERT(false, "This method should never been called");
885             // TableType is a pointer
886             return nullptr;
887         }
888 
889         // destroy_elements is required by the segment_table base class, but unused for unordered containers
890         // this function call but do nothing
891         void destroy_elements() {}
892     }; // struct unordered_segment_table
893 
894     void internal_clear() {
895         // TODO: consider usefulness of two versions of clear() - with dummy nodes deallocation and without it
896         node_ptr next = my_head.next();
897         node_ptr curr = next;
898 
899         my_head.set_next(nullptr);
900 
901         while (curr != nullptr) {
902             next = curr->next();
903             destroy_node(curr);
904             curr = next;
905         }
906 
907         my_size.store(0, std::memory_order_relaxed);
908         my_segments.clear();
909     }
910 
911     void destroy_node( node_ptr node ) {
912         if (node->is_dummy()) {
913             node_allocator_type dummy_node_allocator(my_segments.get_allocator());
914             // Destroy the node
915             node_allocator_traits::destroy(dummy_node_allocator, node);
916             // Deallocate the memory
917             node_allocator_traits::deallocate(dummy_node_allocator, node, 1);
918         } else {
919             // GCC 11.1 issues a warning here that incorrect destructor might be called for dummy_nodes
920             #if (__TBB_GCC_VERSION >= 110100 && __TBB_GCC_VERSION < 120000 ) && !__clang__ && !__INTEL_COMPILER
921             volatile
922             #endif
923             value_node_ptr val_node = static_cast<value_node_ptr>(node);
924             value_node_allocator_type value_node_allocator(my_segments.get_allocator());
925             // Destroy the value
926             value_node_allocator_traits::destroy(value_node_allocator, val_node->storage());
927             // Destroy the node
928             value_node_allocator_traits::destroy(value_node_allocator, val_node);
929             // Deallocate the memory
930             value_node_allocator_traits::deallocate(value_node_allocator, val_node, 1);
931         }
932     }
933 
934     struct internal_insert_return_type {
935         // If the insertion failed - the remaining_node points to the node, which was failed to insert
936         // This node can be allocated in process of insertion
937         value_node_ptr remaining_node;
938         // If the insertion failed - node_with_equal_key points to the node in the list with the
939         // key, equivalent to the inserted, otherwise it points to the node, which was inserted.
940         value_node_ptr node_with_equal_key;
941         // Insertion status
942         // NOTE: if it is true - remaining_node should be nullptr
943         bool inserted;
944     }; // struct internal_insert_return_type
945 
946     // Inserts the value into the split ordered list
947     template <typename ValueType>
948     std::pair<iterator, bool> internal_insert_value( ValueType&& value ) {
949 
950         auto create_value_node = [&value, this]( sokey_type order_key )->value_node_ptr {
951             return create_node(order_key, std::forward<ValueType>(value));
952         };
953 
954         auto insert_result = internal_insert(value, create_value_node);
955 
956         if (insert_result.remaining_node != nullptr) {
957             // If the insertion fails - destroy the node which was failed to insert if it exist
958             __TBB_ASSERT(!insert_result.inserted,
959                          "remaining_node should be nullptr if the node was successfully inserted");
960             destroy_node(insert_result.remaining_node);
961         }
962 
963         return { iterator(insert_result.node_with_equal_key), insert_result.inserted };
964     }
965 
966     // Inserts the node into the split ordered list
967     // Creates a node using the specified callback after the place for insertion was found
968     // Returns internal_insert_return_type object, where:
969     //     - If the insertion succeeded:
970     //         - remaining_node is nullptr
971     //         - node_with_equal_key point to the inserted node
972     //         - inserted is true
973     //     - If the insertion failed:
974     //         - remaining_node points to the node, that was failed to insert if it was created.
975     //           nullptr if the node was not created, because the requested key was already
976     //           presented in the list
977     //         - node_with_equal_key point to the element in the list with the key, equivalent to
978     //           to the requested key
979     //         - inserted is false
980     template <typename ValueType, typename CreateInsertNode>
981     internal_insert_return_type internal_insert( ValueType&& value, CreateInsertNode create_insert_node ) {
982         static_assert(std::is_same<typename std::decay<ValueType>::type, value_type>::value,
983                       "Incorrect type in internal_insert");
984         const key_type& key = traits_type::get_key(value);
985         sokey_type hash_key = sokey_type(my_hash_compare(key));
986 
987         sokey_type order_key = split_order_key_regular(hash_key);
988         node_ptr prev = prepare_bucket(hash_key);
989         __TBB_ASSERT(prev != nullptr, "Invalid head node");
990 
991         auto search_result = search_after(prev, order_key, key);
992 
993         if (search_result.second) {
994             return internal_insert_return_type{ nullptr, search_result.first, false };
995         }
996 
997         value_node_ptr new_node = create_insert_node(order_key);
998         node_ptr curr = search_result.first;
999 
1000         while (!try_insert(prev, new_node, curr)) {
1001             search_result = search_after(prev, order_key, key);
1002             if (search_result.second) {
1003                 return internal_insert_return_type{ new_node, search_result.first, false };
1004             }
1005             curr = search_result.first;
1006         }
1007 
1008         auto sz = my_size.fetch_add(1);
1009         adjust_table_size(sz + 1, my_bucket_count.load(std::memory_order_acquire));
1010         return internal_insert_return_type{ nullptr, static_cast<value_node_ptr>(new_node), true };
1011     }
1012 
1013     // Searches the node with the key, equivalent to key with requested order key after the node prev
1014     // Returns the existing node and true if the node is already in the list
1015     // Returns the first node with the order key, greater than requested and false if the node is not presented in the list
1016     std::pair<value_node_ptr, bool> search_after( node_ptr& prev, sokey_type order_key, const key_type& key ) {
1017         // NOTE: static_cast<value_node_ptr>(curr) should be done only after we would ensure
1018         // that the node is not a dummy node
1019 
1020         node_ptr curr = prev->next();
1021 
1022         while (curr != nullptr && (curr->order_key() < order_key ||
1023                (curr->order_key() == order_key && !my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key))))
1024         {
1025             prev = curr;
1026             curr = curr->next();
1027         }
1028 
1029         if (curr != nullptr && curr->order_key() == order_key && !allow_multimapping) {
1030             return { static_cast<value_node_ptr>(curr), true };
1031         }
1032         return { static_cast<value_node_ptr>(curr), false };
1033     }
1034 
1035     void adjust_table_size( size_type total_elements, size_type current_size ) {
1036         // Grow the table by a factor of 2 if possible and needed
1037         if ( (float(total_elements) / float(current_size)) > my_max_load_factor ) {
1038             // Double the size of the hash only if size hash not changed in between loads
1039             my_bucket_count.compare_exchange_strong(current_size, 2u * current_size);
1040         }
1041     }
1042 
1043     node_ptr insert_dummy_node( node_ptr parent_dummy_node, sokey_type order_key ) {
1044         node_ptr prev_node = parent_dummy_node;
1045 
1046         node_ptr dummy_node = create_dummy_node(order_key);
1047         node_ptr next_node;
1048 
1049         do {
1050             next_node = prev_node->next();
1051             // Move forward through the list while the order key is less than requested
1052             while (next_node != nullptr && next_node->order_key() < order_key) {
1053                 prev_node = next_node;
1054                 next_node = next_node->next();
1055             }
1056 
1057             if (next_node != nullptr && next_node->order_key() == order_key) {
1058                 // Another dummy node with the same order key was inserted by another thread
1059                 // Destroy the node and exit
1060                 destroy_node(dummy_node);
1061                 return next_node;
1062             }
1063         } while (!try_insert(prev_node, dummy_node, next_node));
1064 
1065         return dummy_node;
1066     }
1067 
1068     // Try to insert a node between prev_node and expected next
1069     // If the next is not equal to expected next - return false
1070     static bool try_insert( node_ptr prev_node, node_ptr new_node, node_ptr current_next_node ) {
1071         new_node->set_next(current_next_node);
1072         return prev_node->try_set_next(current_next_node, new_node);
1073     }
1074 
1075     // Returns the bucket, associated with the hash_key
1076     node_ptr prepare_bucket( sokey_type hash_key ) {
1077         size_type bucket = hash_key % my_bucket_count.load(std::memory_order_acquire);
1078         return get_bucket(bucket);
1079     }
1080 
1081     // Initialize the corresponding bucket if it is not initialized
1082     node_ptr get_bucket( size_type bucket_index ) {
1083         if (my_segments[bucket_index].load(std::memory_order_acquire) == nullptr) {
1084             init_bucket(bucket_index);
1085         }
1086         return my_segments[bucket_index].load(std::memory_order_acquire);
1087     }
1088 
1089     void init_bucket( size_type bucket ) {
1090         if (bucket == 0) {
1091             // Atomicaly store the first bucket into my_head
1092             node_ptr disabled = nullptr;
1093             my_segments[0].compare_exchange_strong(disabled, &my_head);
1094             return;
1095         }
1096 
1097         size_type parent_bucket = get_parent(bucket);
1098 
1099         while (my_segments[parent_bucket].load(std::memory_order_acquire) == nullptr) {
1100             // Initialize all of the parent buckets
1101             init_bucket(parent_bucket);
1102         }
1103 
1104         __TBB_ASSERT(my_segments[parent_bucket].load(std::memory_order_acquire) != nullptr, "Parent bucket should be initialized");
1105         node_ptr parent = my_segments[parent_bucket].load(std::memory_order_acquire);
1106 
1107         // Insert dummy node into the list
1108         node_ptr dummy_node = insert_dummy_node(parent, split_order_key_dummy(bucket));
1109         // TODO: consider returning pair<node_ptr, bool> to avoid store operation if the bucket was stored by an other thread
1110         // or move store to insert_dummy_node
1111         // Add dummy_node into the segment table
1112         my_segments[bucket].store(dummy_node, std::memory_order_release);
1113     }
1114 
1115     node_ptr create_dummy_node( sokey_type order_key ) {
1116         node_allocator_type dummy_node_allocator(my_segments.get_allocator());
1117         node_ptr dummy_node = node_allocator_traits::allocate(dummy_node_allocator, 1);
1118         node_allocator_traits::construct(dummy_node_allocator, dummy_node, order_key);
1119         return dummy_node;
1120     }
1121 
1122     template <typename... Args>
1123     value_node_ptr create_node( sokey_type order_key, Args&&... args ) {
1124         value_node_allocator_type value_node_allocator(my_segments.get_allocator());
1125         // Allocate memory for the value_node
1126         value_node_ptr new_node = value_node_allocator_traits::allocate(value_node_allocator, 1);
1127         // Construct the node
1128         value_node_allocator_traits::construct(value_node_allocator, new_node, order_key);
1129 
1130         // try_call API is not convenient here due to broken
1131         // variadic capture on GCC 4.8.5
1132         auto value_guard = make_raii_guard([&] {
1133             value_node_allocator_traits::destroy(value_node_allocator, new_node);
1134             value_node_allocator_traits::deallocate(value_node_allocator, new_node, 1);
1135         });
1136 
1137         // Construct the value in the node
1138         value_node_allocator_traits::construct(value_node_allocator, new_node->storage(), std::forward<Args>(args)...);
1139         value_guard.dismiss();
1140         return new_node;
1141     }
1142 
1143     value_node_ptr first_value_node( node_ptr first_node ) const {
1144         while (first_node != nullptr && first_node->is_dummy()) {
1145             first_node = first_node->next();
1146         }
1147         return static_cast<value_node_ptr>(first_node);
1148     }
1149 
1150     // Unsafe method, which removes the node from the list and returns the next node
1151     node_ptr internal_erase( value_node_ptr node_to_erase ) {
1152         __TBB_ASSERT(node_to_erase != nullptr, "Invalid iterator for erase");
1153         node_ptr next_node = node_to_erase->next();
1154         internal_extract(node_to_erase);
1155         destroy_node(node_to_erase);
1156         return next_node;
1157     }
1158 
1159     template <typename K>
1160     size_type internal_erase_by_key( const K& key ) {
1161         // TODO: consider reimplementation without equal_range - it is not effective to perform lookup over a bucket
1162         // for each unsafe_erase call
1163         auto eq_range = equal_range(key);
1164         size_type erased_count = 0;
1165 
1166         for (auto it = eq_range.first; it != eq_range.second;) {
1167             it = unsafe_erase(it);
1168             ++erased_count;
1169         }
1170         return erased_count;
1171     }
1172 
1173     // Unsafe method, which extracts the node from the list
1174     void internal_extract( value_node_ptr node_to_extract ) {
1175         const key_type& key = traits_type::get_key(node_to_extract->value());
1176         sokey_type hash_key = sokey_type(my_hash_compare(key));
1177 
1178         node_ptr prev_node = prepare_bucket(hash_key);
1179 
1180         for (node_ptr node = prev_node->next(); node != nullptr; prev_node = node, node = node->next()) {
1181             if (node == node_to_extract) {
1182                 unlink_node(prev_node, node, node_to_extract->next());
1183                 my_size.store(my_size.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed);
1184                 return;
1185             }
1186             __TBB_ASSERT(node->order_key() <= node_to_extract->order_key(),
1187                          "node, which is going to be extracted should be presented in the list");
1188         }
1189     }
1190 
1191 protected:
1192     template <typename SourceType>
1193     void internal_merge( SourceType&& source ) {
1194         static_assert(std::is_same<node_type, typename std::decay<SourceType>::type::node_type>::value,
1195                       "Incompatible containers cannot be merged");
1196 
1197         for (node_ptr source_prev = &source.my_head; source_prev->next() != nullptr;) {
1198             if (!source_prev->next()->is_dummy()) {
1199                 value_node_ptr curr = static_cast<value_node_ptr>(source_prev->next());
1200                 // If the multimapping is allowed, or the key is not presented
1201                 // in the *this container - extract the node from the list
1202                 if (allow_multimapping || !contains(traits_type::get_key(curr->value()))) {
1203                     node_ptr next_node = curr->next();
1204                     source.unlink_node(source_prev, curr, next_node);
1205 
1206                     // Remember the old order key
1207                     sokey_type old_order_key = curr->order_key();
1208 
1209                     // Node handle with curr cannot be used directly in insert call, because
1210                     // the destructor of node_type will destroy curr
1211                     node_type curr_node = node_handle_accessor::construct<node_type>(curr);
1212 
1213                     // If the insertion fails - return ownership of the node to the source
1214                     if (!insert(std::move(curr_node)).second) {
1215                         __TBB_ASSERT(!allow_multimapping, "Insertion should succeed for multicontainer");
1216                         __TBB_ASSERT(source_prev->next() == next_node,
1217                                      "Concurrent operations with the source container in merge are prohibited");
1218 
1219                         // Initialize the node with the old order key, because the order key
1220                         // can change during the insertion
1221                         curr->init(old_order_key);
1222                         __TBB_ASSERT(old_order_key >= source_prev->order_key() &&
1223                                      (next_node == nullptr || old_order_key <= next_node->order_key()),
1224                                      "Wrong nodes order in the source container");
1225                         // Merge is unsafe for source container, so the insertion back can be done without compare_exchange
1226                         curr->set_next(next_node);
1227                         source_prev->set_next(curr);
1228                         source_prev = curr;
1229                         node_handle_accessor::deactivate(curr_node);
1230                     } else {
1231                         source.my_size.fetch_sub(1, std::memory_order_relaxed);
1232                     }
1233                 } else {
1234                     source_prev = curr;
1235                 }
1236             } else {
1237                 source_prev = source_prev->next();
1238             }
1239         }
1240     }
1241 
1242 private:
1243     // Unsafe method, which unlinks the node between prev and next
1244     void unlink_node( node_ptr prev_node, node_ptr node_to_unlink, node_ptr next_node ) {
1245         __TBB_ASSERT(prev_node->next() == node_to_unlink &&
1246                      node_to_unlink->next() == next_node,
1247                      "erasing and extracting nodes from the containers are unsafe in concurrent mode");
1248         prev_node->set_next(next_node);
1249         node_to_unlink->set_next(nullptr);
1250     }
1251 
1252     template <typename K>
1253     value_node_ptr internal_find( const K& key ) {
1254         sokey_type hash_key = sokey_type(my_hash_compare(key));
1255         sokey_type order_key = split_order_key_regular(hash_key);
1256 
1257         node_ptr curr = prepare_bucket(hash_key);
1258 
1259         while (curr != nullptr) {
1260             if (curr->order_key() > order_key) {
1261                 // If the order key is greater than the requested order key,
1262                 // the element is not in the hash table
1263                 return nullptr;
1264             } else if (curr->order_key() == order_key &&
1265                        my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)) {
1266                 // The fact that order keys match does not mean that the element is found.
1267                 // Key function comparison has to be performed to check whether this is the
1268                 // right element. If not, keep searching while order key is the same.
1269                 return static_cast<value_node_ptr>(curr);
1270             }
1271             curr = curr->next();
1272         }
1273 
1274         return nullptr;
1275     }
1276 
1277     template <typename K>
1278     std::pair<value_node_ptr, value_node_ptr> internal_equal_range( const K& key ) {
1279         sokey_type hash_key = sokey_type(my_hash_compare(key));
1280         sokey_type order_key = split_order_key_regular(hash_key);
1281 
1282         node_ptr curr = prepare_bucket(hash_key);
1283 
1284         while (curr != nullptr) {
1285             if (curr->order_key() > order_key) {
1286                 // If the order key is greater than the requested order key,
1287                 // the element is not in the hash table
1288                 return std::make_pair(nullptr, nullptr);
1289             } else if (curr->order_key() == order_key &&
1290                        my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)) {
1291                 value_node_ptr first = static_cast<value_node_ptr>(curr);
1292                 node_ptr last = first;
1293                 do {
1294                     last = last->next();
1295                 } while (allow_multimapping && last != nullptr && !last->is_dummy() &&
1296                         my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(last)->value()), key));
1297                 return std::make_pair(first, first_value_node(last));
1298             }
1299             curr = curr->next();
1300         }
1301         return {nullptr, nullptr};
1302     }
1303 
1304     template <typename K>
1305     size_type internal_count( const K& key ) const {
1306         if (allow_multimapping) {
1307             // TODO: consider reimplementing the internal_equal_range with elements counting to avoid std::distance
1308             auto eq_range = equal_range(key);
1309             return std::distance(eq_range.first, eq_range.second);
1310         } else {
1311             return contains(key) ? 1 : 0;
1312         }
1313     }
1314 
1315     void internal_copy( const concurrent_unordered_base& other ) {
1316         node_ptr last_node = &my_head;
1317         my_segments[0].store(&my_head, std::memory_order_relaxed);
1318 
1319         for (node_ptr node = other.my_head.next(); node != nullptr; node = node->next()) {
1320             node_ptr new_node;
1321             if (!node->is_dummy()) {
1322                 // The node in the right table contains a value
1323                 new_node = create_node(node->order_key(), static_cast<value_node_ptr>(node)->value());
1324             } else {
1325                 // The node in the right table is a dummy node
1326                 new_node = create_dummy_node(node->order_key());
1327                 my_segments[reverse_bits(node->order_key())].store(new_node, std::memory_order_relaxed);
1328             }
1329 
1330             last_node->set_next(new_node);
1331             last_node = new_node;
1332         }
1333     }
1334 
1335     void internal_move( concurrent_unordered_base&& other ) {
1336         node_ptr last_node = &my_head;
1337         my_segments[0].store(&my_head, std::memory_order_relaxed);
1338 
1339         for (node_ptr node = other.my_head.next(); node != nullptr; node = node->next()) {
1340             node_ptr new_node;
1341             if (!node->is_dummy()) {
1342                 // The node in the right table contains a value
1343                 new_node = create_node(node->order_key(), std::move(static_cast<value_node_ptr>(node)->value()));
1344             } else {
1345                 // TODO: do we need to destroy a dummy node in the right container?
1346                 // The node in the right table is a dummy_node
1347                 new_node = create_dummy_node(node->order_key());
1348                 my_segments[reverse_bits(node->order_key())].store(new_node, std::memory_order_relaxed);
1349             }
1350 
1351             last_node->set_next(new_node);
1352             last_node = new_node;
1353         }
1354     }
1355 
1356     void move_content( concurrent_unordered_base&& other ) {
1357         // NOTE: allocators should be equal
1358         my_head.set_next(other.my_head.next());
1359         other.my_head.set_next(nullptr);
1360         my_segments[0].store(&my_head, std::memory_order_relaxed);
1361 
1362         other.my_bucket_count.store(initial_bucket_count, std::memory_order_relaxed);
1363         other.my_max_load_factor = initial_max_load_factor;
1364         other.my_size.store(0, std::memory_order_relaxed);
1365     }
1366 
1367     void internal_move_construct_with_allocator( concurrent_unordered_base&& other, const allocator_type&,
1368                                                  /*is_always_equal = */std::true_type ) {
1369         // Allocators are always equal - no need to compare for equality
1370         move_content(std::move(other));
1371     }
1372 
1373     void internal_move_construct_with_allocator( concurrent_unordered_base&& other, const allocator_type& alloc,
1374                                                  /*is_always_equal = */std::false_type ) {
1375         // Allocators are not always equal
1376         if (alloc == other.my_segments.get_allocator()) {
1377             move_content(std::move(other));
1378         } else {
1379             try_call( [&] {
1380                 internal_move(std::move(other));
1381             } ).on_exception( [&] {
1382                 clear();
1383             });
1384         }
1385     }
1386 
1387     // Move assigns the hash table to other is any instances of allocator_type are always equal
1388     // or propagate_on_container_move_assignment is true
1389     void internal_move_assign( concurrent_unordered_base&& other, /*is_always_equal || POCMA = */std::true_type ) {
1390         move_content(std::move(other));
1391     }
1392 
1393     // Move assigns the hash table to other is any instances of allocator_type are not always equal
1394     // and propagate_on_container_move_assignment is false
1395     void internal_move_assign( concurrent_unordered_base&& other, /*is_always_equal || POCMA = */std::false_type ) {
1396         if (my_segments.get_allocator() == other.my_segments.get_allocator()) {
1397             move_content(std::move(other));
1398         } else {
1399             // TODO: guards for exceptions
1400             internal_move(std::move(other));
1401         }
1402     }
1403 
1404     void internal_swap( concurrent_unordered_base& other, /*is_always_equal || POCS = */std::true_type ) {
1405         internal_swap_fields(other);
1406     }
1407 
1408     void internal_swap( concurrent_unordered_base& other, /*is_always_equal || POCS = */std::false_type ) {
1409         __TBB_ASSERT(my_segments.get_allocator() == other.my_segments.get_allocator(),
1410                      "Swapping with unequal allocators is not allowed");
1411         internal_swap_fields(other);
1412     }
1413 
1414     void internal_swap_fields( concurrent_unordered_base& other ) {
1415         node_ptr first_node = my_head.next();
1416         my_head.set_next(other.my_head.next());
1417         other.my_head.set_next(first_node);
1418 
1419         size_type current_size = my_size.load(std::memory_order_relaxed);
1420         my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
1421         other.my_size.store(current_size, std::memory_order_relaxed);
1422 
1423         size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed);
1424         my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed);
1425         other.my_bucket_count.store(bucket_count, std::memory_order_relaxed);
1426 
1427         using std::swap;
1428         swap(my_max_load_factor, other.my_max_load_factor);
1429         swap(my_hash_compare, other.my_hash_compare);
1430         my_segments.swap(other.my_segments);
1431 
1432         // swap() method from segment table swaps all of the segments including the first segment
1433         // We should restore it to my_head. Without it the first segment of the container will point
1434         // to other.my_head.
1435         my_segments[0].store(&my_head, std::memory_order_relaxed);
1436         other.my_segments[0].store(&other.my_head, std::memory_order_relaxed);
1437     }
1438 
1439     // A regular order key has its original hash value reversed and the last bit set
1440     static constexpr sokey_type split_order_key_regular( sokey_type hash ) {
1441         return reverse_bits(hash) | 0x1;
1442     }
1443 
1444     // A dummy order key has its original hash value reversed and the last bit unset
1445     static constexpr sokey_type split_order_key_dummy( sokey_type hash ) {
1446         return reverse_bits(hash) & ~sokey_type(0x1);
1447     }
1448 
1449     size_type get_parent( size_type bucket ) const {
1450         // Unset bucket's most significant turned-on bit
1451         __TBB_ASSERT(bucket != 0, "Unable to get_parent of the bucket 0");
1452         size_type msb = tbb::detail::log2(bucket);
1453         return bucket & ~(size_type(1) << msb);
1454     }
1455 
1456     size_type get_next_bucket_index( size_type bucket ) const {
1457         size_type bits = tbb::detail::log2(my_bucket_count.load(std::memory_order_relaxed));
1458         size_type reversed_next = reverse_n_bits(bucket, bits) + 1;
1459         return reverse_n_bits(reversed_next, bits);
1460     }
1461 
1462     std::atomic<size_type> my_size;
1463     std::atomic<size_type> my_bucket_count;
1464     float my_max_load_factor;
1465     hash_compare_type my_hash_compare;
1466 
1467     list_node_type my_head; // Head node for split ordered list
1468     unordered_segment_table my_segments; // Segment table of pointers to nodes
1469 
1470     template <typename Container, typename Value>
1471     friend class solist_iterator;
1472 
1473     template <typename OtherTraits>
1474     friend class concurrent_unordered_base;
1475 }; // class concurrent_unordered_base
1476 
1477 template <typename Traits>
1478 bool operator==( const concurrent_unordered_base<Traits>& lhs,
1479                  const concurrent_unordered_base<Traits>& rhs ) {
1480     if (&lhs == &rhs) { return true; }
1481     if (lhs.size() != rhs.size()) { return false; }
1482 
1483 #if _MSC_VER
1484     // Passing "unchecked" iterators to std::permutation with 3 parameters
1485     // causes compiler warnings.
1486     // The workaround is to use overload with 4 parameters, which is
1487     // available since C++14 - minimally supported version on MSVC
1488     return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
1489 #else
1490     return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin());
1491 #endif
1492 }
1493 
1494 #if !__TBB_CPP20_COMPARISONS_PRESENT
1495 template <typename Traits>
1496 bool operator!=( const concurrent_unordered_base<Traits>& lhs,
1497                  const concurrent_unordered_base<Traits>& rhs ) {
1498     return !(lhs == rhs);
1499 }
1500 #endif
1501 
1502 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
1503 #pragma warning(pop) // warning 4127 is back
1504 #endif
1505 
1506 } // namespace d1
1507 } // namespace detail
1508 } // namespace tbb
1509 
1510 #endif // __TBB_detail__concurrent_unordered_base_H
1511