1 /*
2     Copyright (c) 2005-2021 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 #if _MSC_VER
18 #if __INTEL_COMPILER
19     #pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
20 #else
21     // Workaround for vs2015 and warning name was longer than the compiler limit (4096).
22     #pragma warning (disable: 4503)
23 #endif
24 #endif
25 
26 #define TBB_DEFINE_STD_HASH_SPECIALIZATIONS 1
27 #define TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 1
28 #include <common/test.h>
29 #include <common/utils.h>
30 #include <common/range_based_for_support.h>
31 #include <common/custom_allocators.h>
32 #include <common/containers_common.h>
33 #include <common/concepts_common.h>
34 #include <tbb/concurrent_hash_map.h>
35 #include <tbb/parallel_for.h>
36 #include <common/concurrent_associative_common.h>
37 #include <vector>
38 #include <list>
39 #include <algorithm>
40 #include <functional>
41 #include <scoped_allocator>
42 #include <mutex>
43 
44 //! \file test_concurrent_hash_map.cpp
45 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification
46 
47 void TestRangeBasedFor(){
48     using namespace range_based_for_support_tests;
49 
50     INFO("testing range based for loop compatibility \n");
51     using ch_map = tbb::concurrent_hash_map<int,int>;
52     ch_map a_ch_map;
53 
54     const int sequence_length = 100;
55     for (int i = 1; i <= sequence_length; ++i){
56         a_ch_map.insert(ch_map::value_type(i,i));
57     }
58 
59     REQUIRE_MESSAGE((range_based_for_accumulate(a_ch_map, pair_second_summer(), 0) == gauss_summ_of_int_sequence(sequence_length)),
60         "incorrect accumulated value generated via range based for ?");
61 }
62 
63 // The helper to run a test only when a default construction is present.
64 template <bool default_construction_present> struct do_default_construction_test {
65     template<typename FuncType> void operator() ( FuncType func ) const { func(); }
66 };
67 
68 template <> struct do_default_construction_test<false> {
69     template<typename FuncType> void operator()( FuncType ) const {}
70 };
71 
72 template <typename Table>
73 class test_insert_by_key {
74     using value_type = typename Table::value_type;
75     Table &my_c;
76     const value_type &my_value;
77 public:
78     test_insert_by_key( Table &c, const value_type &value ) : my_c(c), my_value(value) {}
79     void operator()() const {
80         {
81             typename Table::accessor a;
82             CHECK(my_c.insert( a, my_value.first ));
83             CHECK(utils::IsEqual()(a->first, my_value.first));
84             a->second = my_value.second;
85         }
86         {
87             typename Table::const_accessor ca;
88             CHECK(!my_c.insert( ca, my_value.first ));
89             CHECK(utils::IsEqual()(ca->first, my_value.first));
90             CHECK(utils::IsEqual()(ca->second, my_value.second));
91         }
92     }
93 };
94 
95 template <typename Table, typename Iterator, typename Range = typename Table::range_type>
96 class test_range {
97     using value_type = typename Table::value_type;
98     Table &my_c;
99     const std::list<value_type> &my_lst;
100     std::vector<detail::atomic_type<bool>>& my_marks;
101 public:
102     test_range( Table &c, const std::list<value_type> &lst, std::vector<detail::atomic_type<bool>> &marks ) : my_c(c), my_lst(lst), my_marks(marks) {
103         for (std::size_t i = 0; i < my_marks.size(); ++i) {
104             my_marks[i].store(false, std::memory_order_relaxed);
105         }
106     }
107 
108     void operator()( const Range &r ) const { do_test_range( r.begin(), r.end() ); }
109     void do_test_range( Iterator i, Iterator j ) const {
110         for ( Iterator it = i; it != j; ) {
111             Iterator it_prev = it++;
112             typename std::list<value_type>::const_iterator it2 = std::search( my_lst.begin(), my_lst.end(), it_prev, it, utils::IsEqual() );
113             CHECK(it2 != my_lst.end());
114             typename std::list<value_type>::difference_type dist = std::distance( my_lst.begin(), it2 );
115             CHECK(!my_marks[dist]);
116             my_marks[dist].store(true);
117         }
118     }
119 };
120 
121 template <bool default_construction_present, typename Table>
122 class check_value {
123     using const_iterator = typename Table::const_iterator;
124     using iterator = typename Table::iterator;
125     using size_type = typename Table::size_type;
126     Table &my_c;
127 public:
128     check_value( Table &c ) : my_c(c) {}
129     void operator()(const typename Table::value_type &value ) {
130         const Table &const_c = my_c;
131         CHECK(my_c.count( value.first ) == 1);
132         { // tests with a const accessor.
133             typename Table::const_accessor ca;
134             // find
135             CHECK(my_c.find( ca, value.first ));
136             CHECK(!ca.empty() );
137             CHECK(utils::IsEqual()(ca->first, value.first));
138             CHECK(utils::IsEqual()(ca->second, value.second));
139             // erase
140             CHECK(my_c.erase( ca ));
141             CHECK(my_c.count( value.first ) == 0);
142             // insert (pair)
143             CHECK(my_c.insert( ca, value ));
144             CHECK(utils::IsEqual()(ca->first, value.first));
145             CHECK(utils::IsEqual()(ca->second, value.second));
146         } { // tests with a non-const accessor.
147             typename Table::accessor a;
148             // find
149             CHECK(my_c.find( a, value.first ));
150             CHECK(!a.empty() );
151             CHECK(utils::IsEqual()(a->first, value.first));
152             CHECK(utils::IsEqual()(a->second, value.second));
153             // erase
154             CHECK(my_c.erase( a ));
155             CHECK(my_c.count( value.first ) == 0);
156             // insert
157             CHECK(my_c.insert( a, value ));
158             CHECK(utils::IsEqual()(a->first, value.first));
159             CHECK(utils::IsEqual()(a->second, value.second));
160         }
161         // erase by key
162         CHECK(my_c.erase( value.first ));
163         CHECK(my_c.count( value.first ) == 0);
164         do_default_construction_test<default_construction_present>()(test_insert_by_key<Table>( my_c, value ));
165         // insert by value
166         CHECK(my_c.insert( value ) != default_construction_present);
167         // equal_range
168         std::pair<iterator,iterator> r1 = my_c.equal_range( value.first );
169         iterator r1_first_prev = r1.first++;
170         CHECK((utils::IsEqual()( *r1_first_prev, value ) && utils::IsEqual()( r1.first, r1.second )));
171         std::pair<const_iterator,const_iterator> r2 = const_c.equal_range( value.first );
172         const_iterator r2_first_prev = r2.first++;
173         CHECK((utils::IsEqual()( *r2_first_prev, value ) && utils::IsEqual()( r2.first, r2.second )));
174     }
175 };
176 
177 template <typename Value, typename U = Value>
178 struct CompareTables {
179     template <typename T>
180     static bool IsEqual( const T& t1, const T& t2 ) {
181         return (t1 == t2) && !(t1 != t2);
182     }
183 };
184 
185 template <typename U>
186 struct CompareTables< std::pair<const std::weak_ptr<U>, std::weak_ptr<U> > > {
187     template <typename T>
188     static bool IsEqual( const T&, const T& ) {
189         /* do nothing for std::weak_ptr */
190         return true;
191     }
192 };
193 
194 template <bool default_construction_present, typename Table>
195 void Examine( Table c, const std::list<typename Table::value_type> &lst) {
196     using const_table = const Table;
197     using const_iterator = typename Table::const_iterator;
198     using iterator = typename Table::iterator;
199     using value_type = typename Table::value_type;
200     using size_type = typename Table::size_type;
201 
202     CHECK(!c.empty());
203     CHECK(c.size() == lst.size());
204     CHECK(c.max_size() >= c.size());
205 
206     const check_value<default_construction_present, Table> cv(c);
207     std::for_each( lst.begin(), lst.end(), cv );
208 
209     std::vector<detail::atomic_type<bool>> marks( lst.size() );
210 
211     test_range<Table,iterator>( c, lst, marks ).do_test_range( c.begin(), c.end() );
212     CHECK(std::find( marks.begin(), marks.end(), false ) == marks.end());
213 
214     test_range<const_table,const_iterator>( c, lst, marks ).do_test_range( c.begin(), c.end() );
215     CHECK(std::find( marks.begin(), marks.end(), false ) == marks.end());
216 
217     using range_type = typename Table::range_type;
218     tbb::parallel_for( c.range(), test_range<Table,typename range_type::iterator,range_type>( c, lst, marks ) );
219     CHECK(std::find( marks.begin(), marks.end(), false ) == marks.end());
220 
221     const_table const_c = c;
222     CHECK(CompareTables<value_type>::IsEqual( c, const_c ));
223 
224     const size_type new_bucket_count = 2*c.bucket_count();
225     c.rehash( new_bucket_count );
226     CHECK(c.bucket_count() >= new_bucket_count);
227 
228     Table c2;
229     typename std::list<value_type>::const_iterator begin5 = lst.begin();
230     std::advance( begin5, 5 );
231     c2.insert( lst.begin(), begin5 );
232     std::for_each( lst.begin(), begin5, check_value<default_construction_present, Table>( c2 ) );
233 
234     c2.swap( c );
235     CHECK(CompareTables<value_type>::IsEqual( c2, const_c ));
236     CHECK(c.size() == 5);
237     std::for_each( lst.begin(), lst.end(), check_value<default_construction_present,Table>(c2) );
238 
239     swap( c, c2 );
240     CHECK(CompareTables<value_type>::IsEqual( c, const_c ));
241     CHECK(c2.size() == 5);
242 
243     c2.clear();
244     CHECK(CompareTables<value_type>::IsEqual( c2, Table() ));
245 
246     typename Table::allocator_type a = c.get_allocator();
247     value_type *ptr = a.allocate(1);
248     CHECK(ptr);
249     a.deallocate( ptr, 1 );
250 }
251 
252 template <typename T>
253 struct debug_hash_compare : public tbb::detail::d1::tbb_hash_compare<T> {};
254 
255 template <bool default_construction_present, typename Value>
256 void TypeTester( const std::list<Value> &lst ) {
257     using first_type = typename Value::first_type;
258     using key_type = typename std::remove_const<first_type>::type;
259     using second_type = typename Value::second_type;
260     using ch_map = tbb::concurrent_hash_map<key_type, second_type>;
261     debug_hash_compare<key_type> compare{};
262     // Construct an empty hash map.
263     ch_map c1;
264     c1.insert( lst.begin(), lst.end() );
265     Examine<default_construction_present>( c1, lst );
266 
267     // Constructor from initializer_list.
268     typename std::list<Value>::const_iterator it = lst.begin();
269     std::initializer_list<Value> il = { *it++, *it++, *it++ };
270     ch_map c2( il );
271     c2.insert( it, lst.end() );
272     Examine<default_construction_present>( c2, lst );
273 
274     // Constructor from initializer_list and compare object
275     ch_map c3( il, compare);
276     c3.insert( it, lst.end() );
277     Examine<default_construction_present>( c3, lst );
278 
279     // Constructor from initializer_list, compare object and allocator
280     ch_map c4( il, compare, typename ch_map::allocator_type());
281     c4.insert( it, lst.end());
282     Examine<default_construction_present>( c4, lst );
283 
284     // Copying constructor.
285     ch_map c5(c1);
286     Examine<default_construction_present>( c5, lst );
287     // Construct with non-default allocator
288     using ch_map_debug_alloc = tbb::concurrent_hash_map<key_type, second_type,
289                                                         tbb::detail::d1::tbb_hash_compare<key_type>,
290                                                         LocalCountingAllocator<std::allocator<Value>>>;
291     ch_map_debug_alloc c6;
292     c6.insert( lst.begin(), lst.end() );
293     Examine<default_construction_present>( c6, lst );
294     // Copying constructor
295     ch_map_debug_alloc c7(c6);
296     Examine<default_construction_present>( c7, lst );
297     // Construction empty table with n preallocated buckets.
298     ch_map c8( lst.size() );
299     c8.insert( lst.begin(), lst.end() );
300     Examine<default_construction_present>( c8, lst );
301     ch_map_debug_alloc c9( lst.size() );
302     c9.insert( lst.begin(), lst.end() );
303     Examine<default_construction_present>( c9, lst );
304     // Construction with copying iteration range.
305     ch_map c10_1( c1.begin(), c1.end() ), c10_2(c1.cbegin(), c1.cend());
306     Examine<default_construction_present>( c10_1, lst );
307     Examine<default_construction_present>( c10_2, lst );
308     // Construction with copying iteration range and given allocator instance.
309     LocalCountingAllocator<std::allocator<Value>> allocator;
310     ch_map_debug_alloc c11( lst.begin(), lst.end(), allocator );
311     Examine<default_construction_present>( c11, lst );
312 
313     using ch_map_debug_hash = tbb::concurrent_hash_map<key_type, second_type,
314                                                        debug_hash_compare<key_type>,
315                                                        typename ch_map::allocator_type>;
316 
317     // Constructor with two iterators and hash_compare
318     ch_map_debug_hash c12(c1.begin(), c1.end(), compare);
319     Examine<default_construction_present>( c12, lst );
320 
321     ch_map_debug_hash c13(c1.begin(), c1.end(), compare, typename ch_map::allocator_type());
322     Examine<default_construction_present>( c13, lst );
323 }
324 
325 void TestSpecificTypes() {
326     const int NUMBER = 10;
327 
328     using int_int_t = std::pair<const int, int>;
329     std::list<int_int_t> arrIntInt;
330     for ( int i=0; i<NUMBER; ++i ) arrIntInt.push_back( int_int_t(i, NUMBER-i) );
331     TypeTester</*default_construction_present = */true>( arrIntInt );
332 
333     using ref_int_t = std::pair<const std::reference_wrapper<const int>, int>;
334     std::list<ref_int_t> arrRefInt;
335     for ( std::list<int_int_t>::iterator it = arrIntInt.begin(); it != arrIntInt.end(); ++it )
336         arrRefInt.push_back( ref_int_t( it->first, it->second ) );
337     TypeTester</*default_construction_present = */true>( arrRefInt );
338 
339     using int_ref_t = std::pair< const int, std::reference_wrapper<int> >;
340     std::list<int_ref_t> arrIntRef;
341     for ( std::list<int_int_t>::iterator it = arrIntInt.begin(); it != arrIntInt.end(); ++it )
342         arrIntRef.push_back( int_ref_t( it->first, it->second ) );
343     TypeTester</*default_construction_present = */false>( arrIntRef );
344 
345     using shr_shr_t = std::pair< const std::shared_ptr<int>, std::shared_ptr<int> >;
346     std::list<shr_shr_t> arrShrShr;
347     for ( int i=0; i<NUMBER; ++i ) {
348         const int NUMBER_minus_i = NUMBER - i;
349         arrShrShr.push_back( shr_shr_t( std::make_shared<int>(i), std::make_shared<int>(NUMBER_minus_i) ) );
350     }
351     TypeTester< /*default_construction_present = */true>( arrShrShr );
352 
353     using wk_wk_t = std::pair< const std::weak_ptr<int>, std::weak_ptr<int> >;
354     std::list< wk_wk_t > arrWkWk;
355     std::copy( arrShrShr.begin(), arrShrShr.end(), std::back_inserter(arrWkWk) );
356     TypeTester< /*default_construction_present = */true>( arrWkWk );
357 
358     // Check working with deprecated hashers
359     using pair_key_type = std::pair<int, int>;
360     using pair_int_t = std::pair<const pair_key_type, int>;
361     std::list<pair_int_t> arr_pair_int;
362     for (int i = 0; i < NUMBER; ++i) {
363         arr_pair_int.push_back(pair_int_t(pair_key_type{i, i}, i));
364     }
365     TypeTester</*default_construction_present = */true>(arr_pair_int);
366 
367     using tbb_string_key_type = std::basic_string<char, std::char_traits<char>, tbb::tbb_allocator<char>>;
368     using pair_tbb_string_int_t = std::pair<const tbb_string_key_type, int>;
369     std::list<pair_tbb_string_int_t> arr_pair_string_int;
370     for (int i = 0; i < NUMBER; ++i) {
371         tbb_string_key_type key(i, char(i));
372         arr_pair_string_int.push_back(pair_tbb_string_int_t(key, i));
373     }
374     TypeTester</*default_construction_present = */true>(arr_pair_string_int);
375 }
376 
377 struct custom_hash_compare {
378     template<typename Allocator>
379     size_t hash(const AllocatorAwareData<Allocator>& key) const {
380         return my_hash_compare.hash(key.value());
381     }
382 
383     template<typename Allocator>
384     bool equal(const AllocatorAwareData<Allocator>& key1, const AllocatorAwareData<Allocator>& key2) const {
385         return my_hash_compare.equal(key1.value(), key2.value());
386     }
387 
388 private:
389     tbb::tbb_hash_compare<int> my_hash_compare;
390 };
391 
392 void TestScopedAllocator() {
393     using allocator_data_type = AllocatorAwareData<std::scoped_allocator_adaptor<tbb::tbb_allocator<int>>>;
394     using allocator_type = std::scoped_allocator_adaptor<tbb::tbb_allocator<std::pair<const allocator_data_type, allocator_data_type>>>;
395     using hash_map_type = tbb::concurrent_hash_map<allocator_data_type, allocator_data_type,
396                                                    custom_hash_compare, allocator_type>;
397 
398     allocator_type allocator;
399     allocator_data_type key1(1, allocator), key2(2, allocator);
400     allocator_data_type data1(1, allocator), data2(data1, allocator);
401     hash_map_type map1(allocator), map2(allocator);
402 
403     hash_map_type::value_type v1(key1, data1), v2(key2, data2);
404 
405     auto init_list = { v1, v2 };
406 
407     allocator_data_type::assert_on_constructions = true;
408     map1.emplace(key1, data1);
409     map2.emplace(key2, std::move(data2));
410 
411     map1.clear();
412     map2.clear();
413 
414     map1.insert(v1);
415     map2.insert(std::move(v2));
416 
417     map1.clear();
418     map2.clear();
419 
420     map1.insert(init_list);
421 
422     map1.clear();
423     map2.clear();
424 
425     hash_map_type::accessor a;
426     map2.insert(a, allocator_data_type(3));
427     a.release();
428 
429     map1 = map2;
430     map2 = std::move(map1);
431 
432     hash_map_type map3(allocator);
433     map3.rehash(1000);
434     map3 = map2;
435 }
436 
437 // A test for undocumented member function internal_fast_find
438 // which is declared protected in concurrent_hash_map for internal TBB use
439 void TestInternalFastFind() {
440     typedef tbb::concurrent_hash_map<int, int> basic_chmap_type;
441     typedef basic_chmap_type::const_pointer const_pointer;
442 
443     class chmap : public basic_chmap_type {
444     public:
445         chmap() : basic_chmap_type() {}
446 
447         using basic_chmap_type::internal_fast_find;
448     };
449 
450     chmap m;
451     int sz = 100;
452 
453     for (int i = 0; i != sz; ++i) {
454         m.insert(std::make_pair(i, i * i));
455     }
456     REQUIRE_MESSAGE(m.size() == 100, "Incorrect concurrent_hash_map size");
457 
458     for (int i = 0; i != sz; ++i) {
459         const_pointer res = m.internal_fast_find(i);
460         REQUIRE_MESSAGE(res != nullptr, "Incorrect internal_fast_find return value for existing key");
461         basic_chmap_type::value_type val = *res;
462         REQUIRE_MESSAGE(val.first == i, "Incorrect key in internal_fast_find return value");
463         REQUIRE_MESSAGE(val.second == i * i, "Incorrect mapped in internal_fast_find return value");
464     }
465 
466     for (int i = sz; i != 2 * sz; ++i) {
467         const_pointer res = m.internal_fast_find(i);
468         REQUIRE_MESSAGE(res == nullptr, "Incorrect internal_fast_find return value for not existing key");
469     }
470 }
471 
472 struct default_container_traits {
473     template <typename container_type, typename iterator_type>
474     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
475         container_type* ptr = reinterpret_cast<container_type*>(&storage);
476         new (ptr) container_type(begin, end);
477         return *ptr;
478     }
479 
480     template <typename container_type, typename iterator_type, typename allocator_type>
481     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
482         container_type* ptr = reinterpret_cast<container_type*>(&storage);
483         new (ptr) container_type(begin, end, a);
484         return *ptr;
485     }
486 };
487 
488 struct hash_map_traits : default_container_traits {
489     enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
490 
491     template<typename T>
492     struct hash_compare {
493         bool equal( const T& lhs, const T& rhs ) const {
494             return lhs==rhs;
495         }
496         size_t hash( const T& k ) const {
497             return my_hash_func(k);
498         }
499         std::hash<T> my_hash_func;
500     };
501 
502     template <typename T, typename Allocator>
503     using container_type = tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>;
504 
505     template <typename T>
506     using container_value_type = std::pair<const T, T>;
507 
508     template<typename element_type, typename allocator_type>
509     struct apply {
510         using type = tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>;
511     };
512 
513     using init_iterator_type = move_support_tests::FooPairIterator;
514     template <typename hash_map_type, typename iterator>
515     static bool equal(hash_map_type const& c, iterator begin, iterator end){
516         bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() );
517         if (!equal_sizes)
518             return false;
519 
520         for (iterator it = begin; it != end; ++it ){
521             if (c.count( (*it).first) == 0){
522                 return false;
523             }
524         }
525         return true;
526     }
527 };
528 
529 template <bool IsConstructible>
530 class HeterogeneousKey {
531 public:
532     static std::size_t heterogeneous_keys_count;
533 
534     int integer_key() const { return my_key; }
535 
536     template <bool I = IsConstructible, typename = typename std::enable_if<I>::type>
537     HeterogeneousKey(int key) : my_key(key) { ++heterogeneous_keys_count; }
538 
539     HeterogeneousKey(const HeterogeneousKey&) = delete;
540     HeterogeneousKey& operator=(const HeterogeneousKey&) = delete;
541 
542     static void reset() { heterogeneous_keys_count = 0; }
543 
544     struct construct_flag {};
545 
546     HeterogeneousKey( construct_flag, int key ) : my_key(key) {}
547 
548 private:
549     int my_key;
550 }; // class HeterogeneousKey
551 
552 template <bool IsConstructible>
553 std::size_t HeterogeneousKey<IsConstructible>::heterogeneous_keys_count = 0;
554 
555 struct HeterogeneousHashCompare {
556     using is_transparent = void;
557 
558     template <bool IsConstructible>
559     std::size_t hash( const HeterogeneousKey<IsConstructible>& key ) const {
560         return my_hash_object(key.integer_key());
561     }
562 
563     std::size_t hash( const int& key ) const {
564         return my_hash_object(key);
565     }
566 
567     bool equal( const int& key1, const int& key2 ) const {
568         return key1 == key2;
569     }
570 
571     template <bool IsConstructible>
572     bool equal( const int& key1, const HeterogeneousKey<IsConstructible>& key2 ) const {
573         return key1 == key2.integer_key();
574     }
575 
576     template <bool IsConstructible>
577     bool equal( const HeterogeneousKey<IsConstructible>& key1, const int& key2 ) const {
578         return key1.integer_key() == key2;
579     }
580 
581     template <bool IsConstructible>
582     bool equal( const HeterogeneousKey<IsConstructible>& key1, const HeterogeneousKey<IsConstructible>& key2 ) const {
583         return key1.integer_key() == key2.integer_key();
584     }
585 
586     std::hash<int> my_hash_object;
587 }; // struct HeterogeneousHashCompare
588 
589 class DefaultConstructibleValue {
590 public:
591     DefaultConstructibleValue() : my_i(default_value) {};
592 
593     int value() const { return my_i; }
594     static constexpr int default_value = -4242;
595 private:
596     int my_i;
597 }; // class DefaultConstructibleValue
598 
599 constexpr int DefaultConstructibleValue::default_value;
600 
601 void test_heterogeneous_find() {
602     using key_type = HeterogeneousKey</*IsConstructible = */false>;
603     using chmap_type = tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
604 
605     chmap_type chmap;
606     using const_accessor = typename chmap_type::const_accessor;
607     using accessor = typename chmap_type::accessor;
608     const_accessor cacc;
609     accessor acc;
610 
611     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
612 
613     key_type key(key_type::construct_flag{}, 1);
614     bool regular_result = chmap.find(cacc, key);
615     bool heterogeneous_result = chmap.find(cacc, int(1));
616 
617     REQUIRE(!regular_result);
618     REQUIRE_MESSAGE(regular_result == heterogeneous_result,
619                     "Incorrect heterogeneous find result with const_accessor (no element)");
620     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with const_accessor (no element)");
621 
622     regular_result = chmap.find(acc, key);
623     heterogeneous_result = chmap.find(acc, int(1));
624 
625     REQUIRE(!regular_result);
626     REQUIRE_MESSAGE(regular_result == heterogeneous_result,
627                     "Incorrect heterogeneous find result with accessor (no element)");
628     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with accessor (no element)");
629 
630     bool tmp_result = chmap.emplace(cacc, std::piecewise_construct,
631                                     std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
632     REQUIRE(tmp_result);
633 
634     regular_result = chmap.find(cacc, key);
635     heterogeneous_result = chmap.find(cacc, int(1));
636 
637     REQUIRE(regular_result);
638     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous find result with const_accessor (element exists)");
639     REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor returned");
640     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with const_accessor (element exists)");
641     cacc.release();
642 
643     regular_result = chmap.find(acc, key);
644     heterogeneous_result = chmap.find(acc, int(1));
645 
646     REQUIRE(regular_result);
647     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous find result with accessor (element exists)");
648     REQUIRE_MESSAGE(acc->first.integer_key() == 1, "Incorrect accessor returned");
649     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with accessor (element exists)");
650     key_type::reset();
651 }
652 
653 void test_heterogeneous_count() {
654     using key_type = HeterogeneousKey</*IsConstructible = */false>;
655     using chmap_type = tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
656 
657     chmap_type chmap;
658 
659     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
660     key_type key(key_type::construct_flag{}, 1);
661 
662     typename chmap_type::size_type regular_count = chmap.count(key);
663     typename chmap_type::size_type heterogeneous_count = chmap.count(int(1));
664 
665     REQUIRE(regular_count == 0);
666     REQUIRE_MESSAGE(regular_count == heterogeneous_count, "Incorrect heterogeneous count result (no element)");
667     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during count call (no element)");
668 
669     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
670 
671     regular_count = chmap.count(key);
672     heterogeneous_count = chmap.count(int(1));
673 
674     REQUIRE(regular_count == 1);
675     REQUIRE_MESSAGE(regular_count == heterogeneous_count, "Incorrect heterogeneous count result (element exists)");
676     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during count call (element exists)");
677     key_type::reset();
678 }
679 
680 void test_heterogeneous_equal_range() {
681     using key_type = HeterogeneousKey</*IsConstructible = */false>;
682     using chmap_type = tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
683 
684     chmap_type chmap;
685     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
686 
687     using iterator = typename chmap_type::iterator;
688     using const_iterator = typename chmap_type::const_iterator;
689     using result = std::pair<iterator, iterator>;
690     using const_result = std::pair<const_iterator, const_iterator>;
691     key_type key(key_type::construct_flag{}, 1);
692 
693     result regular_result = chmap.equal_range(key);
694     result heterogeneous_result = chmap.equal_range(int(1));
695 
696     REQUIRE(regular_result.first == chmap.end());
697     REQUIRE(regular_result.second == chmap.end());
698     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous equal_range result (non const, no element)");
699     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (non const, no element)");
700 
701     const chmap_type& cchmap = chmap;
702 
703     const_result regular_const_result = cchmap.equal_range(key);
704     const_result heterogeneous_const_result = cchmap.equal_range(int(1));
705 
706     REQUIRE(regular_const_result.first == cchmap.end());
707     REQUIRE(regular_const_result.second == cchmap.end());
708     REQUIRE_MESSAGE(regular_const_result == heterogeneous_const_result,
709                     "Incorrect heterogeneous equal_range result (const, no element)");
710     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (const, no element)");
711 
712     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
713 
714     regular_result = chmap.equal_range(key);
715     heterogeneous_result = chmap.equal_range(int(1));
716 
717     REQUIRE(regular_result.first != chmap.end());
718     REQUIRE(regular_result.first->first.integer_key() == 1);
719     REQUIRE(regular_result.second == chmap.end());
720     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous equal_range result (non const, element exists)");
721     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (non const, element exists)");
722 
723     regular_const_result = cchmap.equal_range(key);
724     heterogeneous_const_result = cchmap.equal_range(int(1));
725     REQUIRE_MESSAGE(regular_const_result == heterogeneous_const_result,
726                     "Incorrect heterogeneous equal_range result (const, element exists)");
727     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (const, element exists)");
728     key_type::reset();
729 }
730 
731 void test_heterogeneous_insert() {
732     using key_type = HeterogeneousKey</*IsConstructible = */true>;
733     using chmap_type = tbb::concurrent_hash_map<key_type, DefaultConstructibleValue, HeterogeneousHashCompare>;
734 
735     chmap_type chmap;
736     using const_accessor = typename chmap_type::const_accessor;
737     using accessor = typename chmap_type::accessor;
738     const_accessor cacc;
739     accessor acc;
740 
741     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
742 
743     bool result = chmap.insert(cacc, int(1));
744 
745     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 1, "Only one heterogeneous key should be created");
746     REQUIRE_MESSAGE(result, "Incorrect heterogeneous insert result (const_accessor)");
747     REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor");
748     REQUIRE_MESSAGE(cacc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
749 
750     result = chmap.insert(cacc, int(1));
751 
752     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 1, "No extra keys should be created");
753     REQUIRE_MESSAGE(!result, "Incorrect heterogeneous insert result (const_accessor)");
754     REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor");
755     REQUIRE_MESSAGE(cacc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
756 
757     result = chmap.insert(acc, int(2));
758 
759     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 2, "Only one extra heterogeneous key should be created");
760     REQUIRE_MESSAGE(result, "Incorrect heterogeneous insert result (accessor)");
761     REQUIRE_MESSAGE(acc->first.integer_key() == 2, "Incorrect accessor");
762     REQUIRE_MESSAGE(acc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
763 
764     result = chmap.insert(acc, int(2));
765 
766     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 2, "No extra keys should be created");
767     REQUIRE_MESSAGE(!result, "Incorrect heterogeneous insert result (accessor)");
768     REQUIRE_MESSAGE(acc->first.integer_key() == 2, "Incorrect accessor");
769     REQUIRE_MESSAGE(acc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
770 
771     key_type::reset();
772 }
773 
774 void test_heterogeneous_erase() {
775     using key_type = HeterogeneousKey</*IsConstructible = */false>;
776     using chmap_type = tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
777 
778     chmap_type chmap;
779 
780     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
781 
782     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
783     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 2), std::forward_as_tuple(200));
784 
785     typename chmap_type::const_accessor cacc;
786 
787     REQUIRE(chmap.find(cacc, int(1)));
788     REQUIRE(chmap.find(cacc, int(2)));
789 
790     cacc.release();
791 
792     bool result = chmap.erase(int(1));
793     REQUIRE_MESSAGE(result, "Erasure should be successful");
794     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "No extra keys should be created");
795     REQUIRE_MESSAGE(!chmap.find(cacc, int(1)), "Element was not erased");
796 
797 
798     result = chmap.erase(int(1));
799     REQUIRE_MESSAGE(!result, "Erasure should fail");
800     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "No extra keys should be created");
801     key_type::reset();
802 }
803 
804 void test_heterogeneous_lookup() {
805     test_heterogeneous_find();
806     test_heterogeneous_count();
807     test_heterogeneous_equal_range();
808 }
809 
810 template <bool SimulateReacquiring>
811 class MinimalisticMutex {
812 public:
813     static constexpr bool is_rw_mutex = true;
814     static constexpr bool is_recursive_mutex = false;
815     static constexpr bool is_fair_mutex = false;
816 
817     class scoped_lock {
818     public:
819         constexpr scoped_lock() noexcept : my_mutex_ptr(nullptr) {}
820 
821         scoped_lock( MinimalisticMutex& m, bool = true ) : my_mutex_ptr(&m) {
822             my_mutex_ptr->my_mutex.lock();
823         }
824 
825         scoped_lock( const scoped_lock& ) = delete;
826         scoped_lock& operator=( const scoped_lock& ) = delete;
827 
828         ~scoped_lock() {
829             if (my_mutex_ptr) release();
830         }
831 
832         void acquire( MinimalisticMutex& m, bool = true ) {
833             CHECK(my_mutex_ptr == nullptr);
834             my_mutex_ptr = &m;
835             my_mutex_ptr->my_mutex.lock();
836         }
837 
838         bool try_acquire( MinimalisticMutex& m, bool = true ) {
839             if (m.my_mutex.try_lock()) {
840                 my_mutex_ptr = &m;
841                 return true;
842             }
843             return false;
844         }
845 
846         void release() {
847             CHECK(my_mutex_ptr != nullptr);
848             my_mutex_ptr->my_mutex.unlock();
849             my_mutex_ptr = nullptr;
850         }
851 
852         bool upgrade_to_writer() const {
853             // upgrade_to_writer should return false if the mutex simulates
854             // reaquiring the lock on upgrade operation
855             return !SimulateReacquiring;
856         }
857 
858         bool downgrade_to_reader() const {
859             // downgrade_to_reader should return false if the mutex simulates
860             // reaquiring the lock on upgrade operation
861             return !SimulateReacquiring;
862         }
863 
864         bool is_writer() const {
865             CHECK(my_mutex_ptr != nullptr);
866             return true; // Always a writer
867         }
868 
869     private:
870         MinimalisticMutex* my_mutex_ptr;
871     }; // class scoped_lock
872 private:
873     std::mutex my_mutex;
874 }; // class MinimalisticMutex
875 
876 template <bool SimulateReacquiring>
877 void test_with_minimalistic_mutex() {
878     using mutex_type = MinimalisticMutex<SimulateReacquiring>;
879     using chmap_type = tbb::concurrent_hash_map<int, int, tbb::tbb_hash_compare<int>,
880                                                 tbb::tbb_allocator<std::pair<const int, int>>,
881                                                 mutex_type>;
882 
883     chmap_type chmap;
884 
885     // Insert pre-existing elements
886     for (int i = 0; i < 100; ++i) {
887         bool result = chmap.emplace(i, i);
888         CHECK(result);
889     }
890 
891     // Insert elements to erase
892     for (int i = 10000; i < 10005; ++i) {
893         bool result = chmap.emplace(i, i);
894         CHECK(result);
895     }
896 
897     auto thread_body = [&]( const tbb::blocked_range<std::size_t>& range ) {
898         for (std::size_t item = range.begin(); item != range.end(); ++item) {
899             switch(item % 4) {
900                 case 0 :
901                     // Insert new elements
902                     for (int i = 100; i < 200; ++i) {
903                         typename chmap_type::const_accessor acc;
904                         chmap.emplace(acc, i, i);
905                         CHECK(acc->first == i);
906                         CHECK(acc->second == i);
907                     }
908                     break;
909                 case 1 :
910                     // Insert pre-existing elements
911                     for (int i = 0; i < 100; ++i) {
912                         typename chmap_type::const_accessor acc;
913                         bool result = chmap.emplace(acc, i, i * 10000);
914                         CHECK(!result);
915                         CHECK(acc->first == i);
916                         CHECK(acc->second == i);
917                     }
918                     break;
919                 case 2 :
920                     // Find pre-existing elements
921                     for (int i = 0; i < 100; ++i) {
922                         typename chmap_type::const_accessor acc;
923                         bool result = chmap.find(acc, i);
924                         CHECK(result);
925                         CHECK(acc->first == i);
926                         CHECK(acc->second == i);
927                     }
928                     break;
929                 case 3 :
930                     // Erase pre-existing elements
931                     for (int i = 10000; i < 10005; ++i) {
932                         chmap.erase(i);
933                     }
934                 break;
935             }
936         }
937     }; // thread_body
938 
939     tbb::blocked_range<std::size_t> br(0, 1000, 8);
940 
941     tbb::parallel_for(br, thread_body);
942 
943     // Check pre-existing and new elements
944     for (int i = 0; i < 200; ++i) {
945         typename chmap_type::const_accessor acc;
946         bool result = chmap.find(acc, i);
947         REQUIRE_MESSAGE(result, "Some element was unexpectedly removed or not inserted");
948         REQUIRE_MESSAGE(acc->first == i, "Incorrect key");
949         REQUIRE_MESSAGE(acc->second == i, "Incorrect value");
950     }
951 
952     // Check elements for erasure
953     for (int i = 10000; i < 10005; ++i) {
954         typename chmap_type::const_accessor acc;
955         bool result = chmap.find(acc, i);
956         REQUIRE_MESSAGE(!result, "Some element was not removed");
957     }
958 }
959 
960 void test_mutex_customization() {
961     test_with_minimalistic_mutex</*SimulateReacquiring = */false>();
962     test_with_minimalistic_mutex</*SimulateReacquiring = */true>();
963 }
964 
965 //! Test of insert operation
966 //! \brief \ref error_guessing
967 TEST_CASE("testing range based for support"){
968     TestRangeBasedFor();
969 }
970 
971 //! Test concurrent_hash_map with specific key/mapped types
972 //! \brief \ref regression \ref error_guessing
973 TEST_CASE("testing concurrent_hash_map with specific key/mapped types") {
974     TestSpecificTypes();
975 }
976 
977 //! Test work with scoped allocator
978 //! \brief \ref regression
979 TEST_CASE("testing work with scoped allocator") {
980     TestScopedAllocator();
981 }
982 
983 //! Test internal fast find for concurrent_hash_map
984 //! \brief \ref regression
985 TEST_CASE("testing internal fast find for concurrent_hash_map") {
986     TestInternalFastFind();
987 }
988 
989 //! Test constructor with move iterators
990 //! \brief \ref error_guessing
991 TEST_CASE("testing constructor with move iterators"){
992     move_support_tests::test_constructor_with_move_iterators<hash_map_traits>();
993 }
994 
995 #if TBB_USE_EXCEPTIONS
996 //! Test exception in constructors
997 //! \brief \ref regression \ref error_guessing
998 TEST_CASE("Test exception in constructors") {
999     using allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const int, int>>>;
1000     using map_type = tbb::concurrent_hash_map<int, int, tbb::tbb_hash_compare<int>, allocator_type>;
1001 
1002     auto init_list = {std::pair<const int, int>(1, 42), std::pair<const int, int>(2, 42), std::pair<const int, int>(3, 42),
1003         std::pair<const int, int>(4, 42), std::pair<const int, int>(5, 42), std::pair<const int, int>(6, 42)};
1004     map_type map(init_list);
1005 
1006     allocator_type::set_limits(1);
1007     REQUIRE_THROWS_AS( [&] {
1008         map_type map1(map);
1009         utils::suppress_unused_warning(map1);
1010     }(), const std::bad_alloc);
1011 
1012     REQUIRE_THROWS_AS( [&] {
1013         map_type map2(init_list.begin(), init_list.end());
1014         utils::suppress_unused_warning(map2);
1015     }(), const std::bad_alloc);
1016 
1017     tbb::tbb_hash_compare<int> test_hash;
1018 
1019     REQUIRE_THROWS_AS( [&] {
1020         map_type map3(init_list.begin(), init_list.end(), test_hash);
1021         utils::suppress_unused_warning(map3);
1022     }(), const std::bad_alloc);
1023 
1024     REQUIRE_THROWS_AS( [&] {
1025         map_type map4(init_list, test_hash);
1026         utils::suppress_unused_warning(map4);
1027     }(), const std::bad_alloc);
1028 
1029     REQUIRE_THROWS_AS( [&] {
1030         map_type map5(init_list);
1031         utils::suppress_unused_warning(map5);
1032     }(), const std::bad_alloc);
1033 
1034     allocator_type::set_limits(0);
1035     map_type big_map{};
1036     for (std::size_t i = 0; i < 1000; ++i) {
1037         big_map.insert(std::pair<const int, int>(i, 42));
1038     }
1039 
1040     allocator_type::init_counters();
1041     allocator_type::set_limits(300);
1042     REQUIRE_THROWS_AS( [&] {
1043         map_type map6(big_map);
1044         utils::suppress_unused_warning(map6);
1045     }(), const std::bad_alloc);
1046 }
1047 #endif // TBB_USE_EXCEPTIONS
1048 
1049 //! \brief \ref error_guessing
1050 TEST_CASE("swap with NotAlwaysEqualAllocator allocators") {
1051     using allocator_type = NotAlwaysEqualAllocator<std::pair<const int, int>>;
1052     using map_type = tbb::concurrent_hash_map<int, int, tbb::tbb_hash_compare<int>, allocator_type>;
1053 
1054     map_type map1{};
1055     map_type map2({{42, 42}, {24, 42}});
1056     map_type map3(map2);
1057 
1058     swap(map1, map2);
1059 
1060     CHECK(map2.empty());
1061     CHECK(map1 == map3);
1062 }
1063 
1064 //! \brief \ref error_guessing
1065 TEST_CASE("test concurrent_hash_map heterogeneous lookup") {
1066     test_heterogeneous_lookup();
1067 }
1068 
1069 //! \brief \ref error_guessing
1070 TEST_CASE("test concurrent_hash_map heterogeneous insert") {
1071     test_heterogeneous_insert();
1072 }
1073 
1074 //! \brief \ref error_guessing
1075 TEST_CASE("test concurrent_hash_map heterogeneous erase") {
1076     test_heterogeneous_erase();
1077 }
1078 
1079 //! \brief \ref error_guessing
1080 TEST_CASE("test concurrent_hash_map mutex customization") {
1081     test_mutex_customization();
1082 }
1083 
1084 #if __TBB_CPP20_CONCEPTS_PRESENT
1085 template <bool ExpectSatisfies, typename Key, typename Mapped, typename... HCTypes>
1086     requires (... && (utils::well_formed_instantiation<tbb::concurrent_hash_map, Key, Mapped, HCTypes> == ExpectSatisfies))
1087 void test_chmap_hash_compare_constraints() {}
1088 
1089 //! \brief \ref error_guessing
1090 TEST_CASE("tbb::concurrent_hash_map hash_compare constraints") {
1091     using key_type = int;
1092     using mapped_type = int;
1093     using namespace test_concepts::hash_compare;
1094 
1095     test_chmap_hash_compare_constraints</*Expected = */true, /*key = */key_type, /*mapped = */mapped_type,
1096                                         Correct<key_type>, tbb::tbb_hash_compare<key_type>>();
1097 
1098     test_chmap_hash_compare_constraints</*Expected = */false, /*key = */key_type, /*mapped = */mapped_type,
1099                                         NonCopyable<key_type>, NonDestructible<key_type>,
1100                                         NoHash<key_type>, HashNonConst<key_type>, WrongInputHash<key_type>, WrongReturnHash<key_type>,
1101                                         NoEqual<key_type>, EqualNonConst<key_type>,
1102                                         WrongFirstInputEqual<key_type>, WrongSecondInputEqual<key_type>, WrongReturnEqual<key_type>>();
1103 }
1104 
1105 template <bool ExpectSatisfies, typename Key, typename Mapped, typename... RWMutexes>
1106     requires (... && (utils::well_formed_instantiation<tbb::concurrent_hash_map, Key, Mapped,
1107                                                 tbb::tbb_hash_compare<Key>, tbb::tbb_allocator<std::pair<const Key, Mapped>>, RWMutexes> == ExpectSatisfies))
1108 void test_chmap_mutex_constraints() {}
1109 
1110 //! \brief \ref error_guessing
1111 TEST_CASE("tbb::concurrent_hash_map rw_mutex constraints") {
1112     using key_type = int;
1113     using mapped_type = int;
1114     using namespace test_concepts::rw_mutex;
1115 
1116     test_chmap_mutex_constraints</*Expected = */true, key_type, mapped_type,
1117                                  Correct>();
1118 
1119     test_chmap_mutex_constraints</*Expected = */false, key_type, mapped_type,
1120                                  NoScopedLock, ScopedLockNoDefaultCtor, ScopedLockNoMutexCtor,
1121                                  ScopedLockNoDtor, ScopedLockNoAcquire, ScopedLockWrongFirstInputAcquire, ScopedLockWrongSecondInputAcquire, ScopedLockNoTryAcquire,
1122                                  ScopedLockWrongFirstInputTryAcquire, ScopedLockWrongSecondInputTryAcquire, ScopedLockWrongReturnTryAcquire, ScopedLockNoRelease,
1123                                  ScopedLockNoUpgrade, ScopedLockWrongReturnUpgrade, ScopedLockNoDowngrade, ScopedLockWrongReturnDowngrade,
1124                                  ScopedLockNoIsWriter, ScopedLockIsWriterNonConst, ScopedLockWrongReturnIsWriter>();
1125 }
1126 
1127 //! \brief \ref error_guessing
1128 TEST_CASE("container_range concept for tbb::concurrent_hash_map ranges") {
1129     static_assert(test_concepts::container_range<tbb::concurrent_hash_map<int, int>::range_type>);
1130     static_assert(test_concepts::container_range<tbb::concurrent_hash_map<int, int>::const_range_type>);
1131 }
1132 
1133 #endif // __TBB_CPP20_CONCEPTS_PRESENT
1134