1 /*
2     Copyright (c) 2005-2023 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 #include <common/test.h>
18 #include <common/utils.h>
19 #include "common/utils_report.h"
20 #include <common/spin_barrier.h>
21 #include <common/state_trackable.h>
22 #include <common/container_move_support.h>
23 #include <common/containers_common.h>
24 #include <common/initializer_list_support.h>
25 #include <common/vector_types.h>
26 #include <common/test_comparisons.h>
27 #include "oneapi/tbb/concurrent_hash_map.h"
28 #include "oneapi/tbb/global_control.h"
29 #include "oneapi/tbb/parallel_for.h"
30 
31 //! \file conformance_concurrent_hash_map.cpp
32 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification
33 
34 /** Has tightly controlled interface so that we can verify
35     that concurrent_hash_map uses only the required interface. */
36 class MyException : public std::bad_alloc {
37 public:
what() const38     virtual const char *what() const throw() override { return "out of items limit"; }
~MyException()39     virtual ~MyException() throw() {}
40 };
41 
42 /** Has tightly controlled interface so that we can verify
43     that concurrent_hash_map uses only the required interface. */
44 class MyKey {
45 private:
46     int key;
47     friend class MyHashCompare;
48     friend class YourHashCompare;
49 public:
50     MyKey() = default;
51     MyKey( const MyKey& ) = default;
52     void operator=( const MyKey&  ) = delete;
make(int i)53     static MyKey make( int i ) {
54         MyKey result;
55         result.key = i;
56         return result;
57     }
58 
value_of() const59     int value_of() const { return key; }
60 };
61 
62 std::atomic<long> MyDataCount;
63 long MyDataCountLimit = 0;
64 
65 class MyData {
66 protected:
67     friend class MyData2;
68     int data;
69     enum state_t {
70         LIVE=0x1234,
71         DEAD=0x5678
72     } my_state;
73     void operator=( const MyData& );    // Deny access
74 public:
MyData(int i=0)75     MyData(int i = 0) {
76         my_state = LIVE;
77         data = i;
78         if (MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) {
79             TBB_TEST_THROW(MyException{});
80         }
81         ++MyDataCount;
82     }
83 
MyData(const MyData & other)84     MyData( const MyData& other ) {
85         CHECK_FAST(other.my_state==LIVE);
86         my_state = LIVE;
87         data = other.data;
88         if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) {
89             TBB_TEST_THROW(MyException{});
90         }
91         ++MyDataCount;
92     }
93 
~MyData()94     ~MyData() {
95         --MyDataCount;
96         my_state = DEAD;
97     }
98 
make(int i)99     static MyData make( int i ) {
100         MyData result;
101         result.data = i;
102         return result;
103     }
104 
value_of() const105     int value_of() const {
106         CHECK_FAST(my_state==LIVE);
107         return data;
108     }
109 
set_value(int i)110     void set_value( int i ) {
111         CHECK_FAST(my_state==LIVE);
112         data = i;
113     }
114 
operator ==(const MyData & other) const115     bool operator==( const MyData& other ) const {
116         CHECK_FAST(other.my_state==LIVE);
117         CHECK_FAST(my_state==LIVE);
118         return data == other.data;
119     }
120 };
121 
122 class MyData2 : public MyData {
123 public:
MyData2()124     MyData2( ) {}
125 
MyData2(const MyData2 & other)126     MyData2( const MyData2& other ) : MyData() {
127         CHECK_FAST(other.my_state==LIVE);
128         CHECK_FAST(my_state==LIVE);
129         data = other.data;
130     }
131 
MyData2(const MyData & other)132     MyData2( const MyData& other ) {
133         CHECK_FAST(other.my_state==LIVE);
134         CHECK_FAST(my_state==LIVE);
135         data = other.data;
136     }
137 
operator =(const MyData & other)138     void operator=( const MyData& other ) {
139         CHECK_FAST(other.my_state==LIVE);
140         CHECK_FAST(my_state==LIVE);
141         data = other.data;
142     }
143 
operator =(const MyData2 & other)144     void operator=( const MyData2& other ) {
145         CHECK_FAST(other.my_state==LIVE);
146         CHECK_FAST(my_state==LIVE);
147         data = other.data;
148     }
149 
operator ==(const MyData2 & other) const150     bool operator==( const MyData2& other ) const {
151         CHECK_FAST(other.my_state==LIVE);
152         CHECK_FAST(my_state==LIVE);
153         return data == other.data;
154     }
155 };
156 
157 class MyHashCompare {
158 public:
equal(const MyKey & j,const MyKey & k) const159     bool equal( const MyKey& j, const MyKey& k ) const {
160         return j.key==k.key;
161     }
162 
hash(const MyKey & k) const163     std::size_t hash( const MyKey& k ) const {
164         return k.key;
165     }
166 };
167 
168 class YourHashCompare {
169 public:
equal(const MyKey & j,const MyKey & k) const170     bool equal( const MyKey& j, const MyKey& k ) const {
171         return j.key==k.key;
172     }
173 
hash(const MyKey &) const174     std::size_t hash( const MyKey& ) const {
175         return 1;
176     }
177 };
178 
179 using test_allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData>>>;
180 using test_table_type = oneapi::tbb::concurrent_hash_map<MyKey, MyData, MyHashCompare, test_allocator_type>;
181 using other_test_table_type = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare>;
182 
183 template <template <typename...> class ContainerType>
test_member_types()184 void test_member_types() {
185     using container_type = ContainerType<int, int>;
186     static_assert(std::is_same<typename container_type::allocator_type, oneapi::tbb::tbb_allocator<std::pair<const int, int>>>::value,
187                   "Incorrect default template allocator");
188 
189     static_assert(std::is_same<typename container_type::key_type, int>::value,
190                   "Incorrect container key_type member type");
191     static_assert(std::is_same<typename container_type::value_type, std::pair<const int, int>>::value,
192                   "Incorrect container value_type member type");
193 
194     static_assert(std::is_unsigned<typename container_type::size_type>::value,
195                   "Incorrect container size_type member type");
196     static_assert(std::is_signed<typename container_type::difference_type>::value,
197                   "Incorrect container difference_type member type");
198 
199     using value_type = typename container_type::value_type;
200     static_assert(std::is_same<typename container_type::reference, value_type&>::value,
201                   "Incorrect container reference member type");
202     static_assert(std::is_same<typename container_type::const_reference, const value_type&>::value,
203                   "Incorrect container const_reference member type");
204     using allocator_type = typename container_type::allocator_type;
205     static_assert(std::is_same<typename container_type::pointer, typename std::allocator_traits<allocator_type>::pointer>::value,
206                   "Incorrect container pointer member type");
207     static_assert(std::is_same<typename container_type::const_pointer, typename std::allocator_traits<allocator_type>::const_pointer>::value,
208                   "Incorrect container const_pointer member type");
209 
210     static_assert(utils::is_forward_iterator<typename container_type::iterator>::value,
211                   "Incorrect container iterator member type");
212     static_assert(!std::is_const<typename container_type::iterator::value_type>::value,
213                   "Incorrect container iterator member type");
214     static_assert(utils::is_forward_iterator<typename container_type::const_iterator>::value,
215                   "Incorrect container const_iterator member type");
216     static_assert(std::is_const<typename container_type::const_iterator::value_type>::value,
217                   "Incorrect container iterator member type");
218 }
219 
220 template<typename test_table_type>
FillTable(test_table_type & x,int n)221 void FillTable( test_table_type& x, int n ) {
222     for( int i=1; i<=n; ++i ) {
223         MyKey key( MyKey::make(-i) ); // hash values must not be specified in direct order
224         typename test_table_type::accessor a;
225         bool b = x.insert(a,key);
226         CHECK_FAST(b);
227         a->second.set_value( i*i );
228     }
229 }
230 
231 template<typename test_table_type>
CheckTable(const test_table_type & x,int n)232 static void CheckTable( const test_table_type& x, int n ) {
233     REQUIRE_MESSAGE( x.size()==size_t(n), "table is different size than expected" );
234     CHECK(x.empty()==(n==0));
235     CHECK(x.size()<=x.max_size());
236     for( int i=1; i<=n; ++i ) {
237         MyKey key( MyKey::make(-i) );
238         typename test_table_type::const_accessor a;
239         bool b = x.find(a,key);
240         CHECK_FAST(b);
241         CHECK_FAST(a->second.value_of()==i*i);
242     }
243     int count = 0;
244     int key_sum = 0;
245     for( typename test_table_type::const_iterator i(x.begin()); i!=x.end(); ++i ) {
246         ++count;
247         key_sum += -i->first.value_of();
248     }
249     CHECK(count==n);
250     CHECK(key_sum==n*(n+1)/2);
251 }
252 
TestCopy()253 void TestCopy() {
254     INFO("testing copy\n");
255     test_table_type t1;
256     for( int i=0; i<10000; i=(i<100 ? i+1 : i*3) ) {
257         MyDataCount = 0;
258 
259         FillTable(t1,i);
260         // Do not call CheckTable(t1,i) before copying, it enforces rehashing
261 
262         test_table_type t2(t1);
263         // Check that copy constructor did not mangle source table.
264         CheckTable(t1,i);
265         swap(t1, t2);
266         CheckTable(t1,i);
267         CHECK(!(t1 != t2));
268 
269         // Clear original table
270         t2.clear();
271         swap(t2, t1);
272         CheckTable(t1,0);
273 
274         // Verify that copy of t1 is correct, even after t1 is cleared.
275         CheckTable(t2,i);
276         t2.clear();
277         t1.swap( t2 );
278         CheckTable(t1,0);
279         CheckTable(t2,0);
280         REQUIRE_MESSAGE( MyDataCount==0, "data leak?" );
281     }
282 }
283 
TestRehash()284 void TestRehash() {
285     INFO("testing rehashing\n");
286     test_table_type w;
287     w.insert( std::make_pair(MyKey::make(-5), MyData()) );
288     w.rehash(); // without this, assertion will fail
289     test_table_type::iterator it = w.begin();
290     int i = 0; // check for non-rehashed buckets
291     for( ; it != w.end(); i++ )
292         w.count( (it++)->first );
293     CHECK(i == 1);
294     for( i=0; i<1000; i=(i<29 ? i+1 : i*2) ) {
295         for( int j=std::max(256+i, i*2); j<10000; j*=3 ) {
296             test_table_type v;
297             FillTable( v, i );
298             CHECK(int(v.size()) == i);
299             CHECK(int(v.bucket_count()) <= j);
300             v.rehash( j );
301             CHECK(int(v.bucket_count()) >= j);
302             CheckTable( v, i );
303         }
304     }
305 }
306 
TestAssignment()307 void TestAssignment() {
308     INFO("testing assignment\n");
309     oneapi::tbb::concurrent_hash_map<int, int> test_map({{1, 2}, {2, 4}});
310     test_map.operator=(test_map); // suppress self assign warning
311     CHECK(!test_map.empty());
312 
313     for( int i=0; i<1000; i=(i<30 ? i+1 : i*5) ) {
314         for( int j=0; j<1000; j=(j<30 ? j+1 : j*7) ) {
315             test_table_type t1;
316             test_table_type t2;
317             FillTable(t1,i);
318             FillTable(t2,j);
319             CHECK((t1 == t2) == (i == j));
320             CheckTable(t2,j);
321 
322             test_table_type& tref = t2=t1;
323             CHECK(&tref==&t2);
324             CHECK(t1 == t2);
325             CheckTable(t1,i);
326             CheckTable(t2,i);
327 
328             t1.clear();
329             CheckTable(t1,0);
330             CheckTable(t2,i);
331             REQUIRE_MESSAGE( MyDataCount==i, "data leak?" );
332 
333             t2.clear();
334             CheckTable(t1,0);
335             CheckTable(t2,0);
336             REQUIRE_MESSAGE( MyDataCount==0, "data leak?" );
337         }
338     }
339 }
340 
341 template<typename Iterator, typename T>
TestIteratorTraits()342 void TestIteratorTraits() {
343     T x;
344     typename Iterator::reference xr = x;
345     typename Iterator::pointer xp = &x;
346     CHECK(&xr==xp);
347 }
348 
349 template<typename Iterator1, typename Iterator2>
TestIteratorAssignment(Iterator2 j)350 void TestIteratorAssignment( Iterator2 j ) {
351     Iterator1 i(j), k;
352     CHECK(i==j);
353     CHECK(!(i!=j));
354     k = j;
355     CHECK(k==j);
356     CHECK(!(k!=j));
357 }
358 
359 template<typename Range1, typename Range2>
TestRangeAssignment(Range2 r2)360 void TestRangeAssignment( Range2 r2 ) {
361     Range1 r1(r2); r1 = r2;
362 }
363 
TestIteratorsAndRanges()364 void TestIteratorsAndRanges() {
365     INFO("testing iterators compliance\n");
366     TestIteratorTraits<test_table_type::iterator,test_table_type::value_type>();
367     TestIteratorTraits<test_table_type::const_iterator,const test_table_type::value_type>();
368 
369     test_table_type v;
370     CHECK(v.range().grainsize() == 1);
371     test_table_type const &u = v;
372 
373     TestIteratorAssignment<test_table_type::const_iterator>( u.begin() );
374     TestIteratorAssignment<test_table_type::const_iterator>( v.begin() );
375     TestIteratorAssignment<test_table_type::iterator>( v.begin() );
376     // doesn't compile as expected: TestIteratorAssignment<typename V::iterator>( u.begin() );
377 
378     // check for non-existing
379     CHECK(v.equal_range(MyKey::make(-1)) == std::make_pair(v.end(), v.end()));
380     CHECK(u.equal_range(MyKey::make(-1)) == std::make_pair(u.end(), u.end()));
381 
382     INFO("testing ranges compliance\n");
383     TestRangeAssignment<test_table_type::const_range_type>( u.range() );
384     TestRangeAssignment<test_table_type::range_type>( v.range() );
385     // doesn't compile as expected: TestRangeAssignment<typename V::range_type>( u.range() );
386 
387     INFO("testing construction and insertion from iterators range\n");
388     FillTable( v, 1000 );
389     other_test_table_type t(v.begin(), v.end());
390     v.rehash();
391     CheckTable(t, 1000);
392     t.insert(v.begin(), v.end()); // do nothing
393     CheckTable(t, 1000);
394     t.clear();
395     t.insert(v.begin(), v.end()); // restore
396     CheckTable(t, 1000);
397 
398     INFO("testing comparison\n");
399     using test_allocator_type2 = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData2>>>;
400     using YourTable1 = oneapi::tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare, test_allocator_type2>;
401     using YourTable2 = oneapi::tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare>;
402     YourTable1 t1;
403     FillTable( t1, 10 );
404     CheckTable(t1, 10 );
405     YourTable2 t2(t1.begin(), t1.end());
406     MyKey key( MyKey::make(-5) ); MyData2 data;
407     CHECK(t2.erase(key));
408     YourTable2::accessor a;
409     CHECK(t2.insert(a, key));
410     data.set_value(0);   a->second = data;
411     CHECK(t1 != t2);
412     data.set_value(5*5); a->second = data;
413     CHECK(t1 == t2);
414 }
415 
416 struct test_insert {
417     template<typename container_type, typename element_type>
testtest_insert418     static void test( std::initializer_list<element_type> il, container_type const& expected ) {
419         container_type vd;
420         vd.insert( il );
421         REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" );
422     }
423 };
424 
425 struct ctor_test {
426  template<typename container_type, typename element_type>
testctor_test427     static void test( std::initializer_list<element_type> il, container_type const& expected ) {
428         container_type vd(il, tbb::tbb_allocator<std::pair<element_type, element_type>>{});
429         REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" );
430     }
431 };
432 
TestInitList()433 void TestInitList(){
434     using namespace initializer_list_support_tests;
435     INFO("testing initializer_list methods \n");
436 
437     using ch_map_type = oneapi::tbb::concurrent_hash_map<int,int>;
438     std::initializer_list<ch_map_type::value_type> pairs_il = {{1,1},{2,2},{3,3},{4,4},{5,5}};
439 
440     test_initializer_list_support_without_assign<ch_map_type, test_insert>( pairs_il );
441     test_initializer_list_support_without_assign<ch_map_type, test_insert>( {} );
442     test_initializer_list_support_without_assign<ch_map_type, ctor_test>(pairs_il);
443 }
444 
445 template <typename base_alloc_type>
446 class only_node_counting_allocator : public StaticSharedCountingAllocator<base_alloc_type> {
447     using base_type = StaticSharedCountingAllocator<base_alloc_type>;
448     using base_traits = oneapi::tbb::detail::allocator_traits<base_alloc_type>;
449 public:
450     template<typename U>
451     struct rebind {
452         using other = only_node_counting_allocator<typename base_traits::template rebind_alloc<U>>;
453     };
454 
only_node_counting_allocator()455     only_node_counting_allocator() : base_type() {}
only_node_counting_allocator(const only_node_counting_allocator & a)456     only_node_counting_allocator(const only_node_counting_allocator& a) : base_type(a) {}
457 
458     template<typename U>
only_node_counting_allocator(const only_node_counting_allocator<U> & a)459     only_node_counting_allocator(const only_node_counting_allocator<U>& a) : base_type(a) {}
460 
allocate(const std::size_t n)461     typename base_type::value_type* allocate(const std::size_t n) {
462         if ( n > 1) {
463             return base_alloc_type::allocate(n);
464         } else {
465             return base_type::allocate(n);
466         }
467     }
468 };
469 
470 #if TBB_USE_EXCEPTIONS
TestExceptions()471 void TestExceptions() {
472     using allocator_type = only_node_counting_allocator<oneapi::tbb::tbb_allocator<std::pair<const MyKey, MyData2>>>;
473     using throwing_table = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare, allocator_type>;
474     enum methods {
475         zero_method = 0,
476         ctor_copy, op_assign, op_insert,
477         all_methods
478     };
479 
480     INFO("testing exception-safety guarantees\n");
481     throwing_table src;
482     FillTable( src, 1000 );
483     CHECK(MyDataCount==1000);
484 
485     try {
486         for(int t = 0; t < 2; t++) // exception type
487         for(int m = zero_method+1; m < all_methods; m++)
488         {
489             allocator_type a;
490             allocator_type::init_counters();
491             if(t) MyDataCountLimit = 101;
492             else a.set_limits(101);
493             throwing_table victim(a);
494             MyDataCount = 0;
495 
496             try {
497                 switch(m) {
498                 case ctor_copy: {
499                         throwing_table acopy(src, a);
500                     } break;
501                 case op_assign: {
502                         victim = src;
503                     } break;
504                 case op_insert: {
505                         // Insertion in cpp11 don't make copy constructions
506                         // during the insertion, so we need to decrement limit
507                         // to throw an exception in the right place and to prevent
508                         // successful insertion of one unexpected item
509                         if (MyDataCountLimit)
510                             --MyDataCountLimit;
511                         FillTable( victim, 1000 );
512                     } break;
513                 default:;
514                 }
515                 REQUIRE_MESSAGE(false, "should throw an exception");
516             } catch(std::bad_alloc &e) {
517                 MyDataCountLimit = 0;
518                 size_t size = victim.size();
519                 switch(m) {
520                 case op_assign:
521                     REQUIRE_MESSAGE( MyDataCount==100, "data leak?" );
522                     CHECK(size>=100);
523                     utils_fallthrough;
524                 case ctor_copy:
525                     CheckTable(src, 1000);
526                     break;
527                 case op_insert:
528                     CHECK(size==size_t(100-t));
529                     REQUIRE_MESSAGE( MyDataCount==100-t, "data leak?" );
530                     CheckTable(victim, 100-t);
531                     break;
532 
533                 default:; // nothing to check here
534                 }
535                 INFO("Exception "<< m << " : " << e.what() << "- ok ()");
536             }
537             catch ( ... ) {
538                 REQUIRE_MESSAGE( false, "Unrecognized exception" );
539             }
540         }
541     } catch(...) {
542         REQUIRE_MESSAGE(false, "unexpected exception");
543     }
544     src.clear(); MyDataCount = 0;
545     allocator_type::max_items = 0;
546 }
547 #endif
548 
549 struct default_container_traits {
550     template <typename container_type, typename iterator_type>
construct_containerdefault_container_traits551     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
552         container_type* ptr = reinterpret_cast<container_type*>(&storage);
553         new (ptr) container_type(begin, end);
554         return *ptr;
555     }
556 
557     template <typename container_type, typename iterator_type, typename allocator_type>
construct_containerdefault_container_traits558     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
559         container_type* ptr = reinterpret_cast<container_type*>(&storage);
560         new (ptr) container_type(begin, end, a);
561         return *ptr;
562     }
563 };
564 
565 struct hash_map_traits : default_container_traits {
566     enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
567 
568     template<typename T>
569     struct hash_compare {
equalhash_map_traits::hash_compare570         bool equal( const T& lhs, const T& rhs ) const {
571             return lhs==rhs;
572         }
hashhash_map_traits::hash_compare573         size_t hash( const T& k ) const {
574             return my_hash_func(k);
575         }
576         std::hash<T> my_hash_func;
577     };
578 
579     template <typename T, typename Allocator>
580     using container_type = oneapi::tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>;
581 
582     template <typename T>
583     using container_value_type = std::pair<const T, T>;
584 
585     template<typename element_type, typename allocator_type>
586     struct apply {
587         using type = oneapi::tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>;
588     };
589 
590     using init_iterator_type = move_support_tests::FooPairIterator;
591     template <typename hash_map_type, typename iterator>
equalhash_map_traits592     static bool equal(hash_map_type const& c, iterator begin, iterator end){
593         bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() );
594         if (!equal_sizes)
595             return false;
596 
597         for (iterator it = begin; it != end; ++it ){
598             if (c.count( (*it).first) == 0){
599                 return false;
600             }
601         }
602         return true;
603     }
604 };
605 
606 using DataStateTrackedTable = oneapi::tbb::concurrent_hash_map<MyKey, move_support_tests::Foo, MyHashCompare>;
607 
608 struct RvalueInsert {
applyRvalueInsert609     static void apply( DataStateTrackedTable& table, int i ) {
610         DataStateTrackedTable::accessor a;
611         int next = i + 1;
612         CHECK_FAST_MESSAGE((table.insert( a, std::make_pair(MyKey::make(i), move_support_tests::Foo(next)))),
613             "already present while should not ?" );
614         CHECK_FAST((*a).second == next);
615         CHECK_FAST((*a).second.state == StateTrackableBase::MoveInitialized);
616     }
617 };
618 
619 struct Emplace {
620     template <typename Accessor>
apply_implEmplace621     static void apply_impl( DataStateTrackedTable& table, int i) {
622         Accessor a;
623         CHECK_FAST_MESSAGE((table.emplace( a, MyKey::make(i), (i + 1))),
624                 "already present while should not ?" );
625         CHECK_FAST((*a).second == i + 1);
626         CHECK_FAST((*a).second.state == StateTrackableBase::DirectInitialized);
627     }
628 
applyEmplace629     static void apply( DataStateTrackedTable& table, int i ) {
630         // TODO: investigate ability to rewrite apply methods with use apply_imp method.
631         if (i % 2) {
632             apply_impl<DataStateTrackedTable::accessor>(table, i);
633         } else {
634             apply_impl<DataStateTrackedTable::const_accessor>(table, i);
635         }
636     }
637 };
638 
639 template<typename Op, typename test_table_type>
640 class TableOperation {
641     test_table_type& my_table;
642 public:
operator ()(const oneapi::tbb::blocked_range<int> & range) const643     void operator()( const oneapi::tbb::blocked_range<int>& range ) const {
644         for( int i=range.begin(); i!=range.end(); ++i )
645             Op::apply(my_table,i);
646     }
TableOperation(test_table_type & table)647     TableOperation( test_table_type& table ) : my_table(table) {}
648 };
649 
UseKey(size_t i)650 bool UseKey( size_t i ) {
651     return (i&3)!=3;
652 }
653 
654 struct Insert {
applyInsert655     static void apply( test_table_type& table, int i ) {
656         if( UseKey(i) ) {
657             if( i&4 ) {
658                 test_table_type::accessor a;
659                 table.insert( a, MyKey::make(i) );
660                 if( i&1 )
661                     (*a).second.set_value(i*i);
662                 else
663                     a->second.set_value(i*i);
664             } else
665                 if( i&1 ) {
666                     test_table_type::accessor a;
667                     table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) );
668                     CHECK_FAST((*a).second.value_of()==i*i);
669                 } else {
670                     test_table_type::const_accessor ca;
671                     table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) );
672                     CHECK_FAST(ca->second.value_of()==i*i);
673                 }
674         }
675     }
676 };
677 
678 struct Find {
applyFind679     static void apply( test_table_type& table, int i ) {
680         test_table_type::accessor a;
681         const test_table_type::accessor& ca = a;
682         bool b = table.find( a, MyKey::make(i) );
683         CHECK_FAST(b==!a.empty());
684         if( b ) {
685             if( !UseKey(i) )
686                 REPORT("Line %d: unexpected key %d present\n",__LINE__,i);
687             CHECK_FAST(ca->second.value_of()==i*i);
688             CHECK_FAST((*ca).second.value_of()==i*i);
689             if( i&1 )
690                 ca->second.set_value( ~ca->second.value_of() );
691             else
692                 (*ca).second.set_value( ~ca->second.value_of() );
693         } else {
694             if( UseKey(i) )
695                 REPORT("Line %d: key %d missing\n",__LINE__,i);
696         }
697     }
698 };
699 
700 struct FindConst {
applyFindConst701     static void apply( const test_table_type& table, int i ) {
702         test_table_type::const_accessor a;
703         const test_table_type::const_accessor& ca = a;
704         bool b = table.find( a, MyKey::make(i) );
705         CHECK_FAST(b==(table.count(MyKey::make(i))>0));
706         CHECK_FAST(b==!a.empty());
707         CHECK_FAST(b==UseKey(i));
708         if( b ) {
709             CHECK_FAST(ca->second.value_of()==~(i*i));
710             CHECK_FAST((*ca).second.value_of()==~(i*i));
711         }
712     }
713 };
714 
715 struct InsertInitList {
applyInsertInitList716     static void apply( test_table_type& table, int i ) {
717         if ( UseKey( i ) ) {
718             // TODO: investigate why the following sequence causes an additional allocation sometimes:
719             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i ) );
720             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i+1 ) );
721             std::initializer_list<test_table_type::value_type> il = {
722                 test_table_type::value_type( MyKey::make( i ), i*i )
723                 /*, test_table_type::value_type( MyKey::make( i ), i*i+1 ) */
724                                                                     };
725             table.insert( il );
726         }
727     }
728 };
729 
730 template<typename Op, typename TableType>
DoConcurrentOperations(TableType & table,int n,const char * what,std::size_t nthread)731 void DoConcurrentOperations( TableType& table, int n, const char* what, std::size_t nthread ) {
732     INFO("testing " << what << " with " << nthread << " threads");
733     oneapi::tbb::parallel_for(oneapi::tbb::blocked_range<int>(0, n ,100), TableOperation<Op, TableType>(table));
734 }
735 
736 //! Test traversing the table with an iterator.
TraverseTable(test_table_type & table,size_t n,size_t expected_size)737 void TraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
738     INFO("testing traversal\n");
739     size_t actual_size = table.size();
740     CHECK(actual_size==expected_size);
741     size_t count = 0;
742     bool* array = new bool[n];
743     memset( array, 0, n*sizeof(bool) );
744     const test_table_type& const_table = table;
745     test_table_type::const_iterator ci = const_table.begin();
746     for( test_table_type::iterator i = table.begin(); i!=table.end(); ++i ) {
747         // Check iterator
748         int k = i->first.value_of();
749         CHECK_FAST(UseKey(k));
750         CHECK_FAST((*i).first.value_of()==k);
751         CHECK_FAST_MESSAGE((0<=k && size_t(k)<n), "out of bounds key" );
752         CHECK_FAST_MESSAGE( !array[k], "duplicate key" );
753         array[k] = true;
754         ++count;
755 
756         // Check lower/upper bounds
757         std::pair<test_table_type::iterator, test_table_type::iterator> er = table.equal_range(i->first);
758         std::pair<test_table_type::const_iterator, test_table_type::const_iterator> cer = const_table.equal_range(i->first);
759         CHECK_FAST((cer.first == er.first && cer.second == er.second));
760         CHECK_FAST(cer.first == i);
761         CHECK_FAST(std::distance(cer.first, cer.second) == 1);
762 
763         // Check const_iterator
764         test_table_type::const_iterator cic = ci++;
765         CHECK_FAST(cic->first.value_of()==k);
766         CHECK_FAST((*cic).first.value_of()==k);
767     }
768     CHECK(ci==const_table.end());
769     delete[] array;
770     if (count != expected_size) {
771         INFO("Line " << __LINE__ << ": count=" << count << " but should be " << expected_size);
772     }
773 }
774 
775 std::atomic<int> EraseCount;
776 
777 struct Erase {
applyErase778     static void apply( test_table_type& table, int i ) {
779         bool b;
780         if(i&4) {
781             if(i&8) {
782                 test_table_type::const_accessor a;
783                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
784             } else {
785                 test_table_type::accessor a;
786                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
787             }
788         } else
789             b = table.erase( MyKey::make(i) );
790         if( b ) ++EraseCount;
791         CHECK_FAST(table.count(MyKey::make(i)) == 0);
792     }
793 };
794 
795 using YourTable = oneapi::tbb::concurrent_hash_map<MyKey, MyData, YourHashCompare>;
796 static const int IE_SIZE = 2;
797 std::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE];
798 
799 struct InsertErase  {
applyInsertErase800     static void apply( YourTable& table, int i ) {
801         if ( i%3 ) {
802             int key = i%IE_SIZE;
803             if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) )
804                 ++InsertEraseCount[key];
805         } else {
806             int key = i%IE_SIZE;
807             if( i&1 ) {
808                 YourTable::accessor res;
809                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
810                     --InsertEraseCount[key];
811             } else {
812                 YourTable::const_accessor res;
813                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
814                     --InsertEraseCount[key];
815             }
816         }
817     }
818 };
819 
820 struct InnerInsert {
applyInnerInsert821     static void apply( YourTable& table, int i ) {
822         YourTable::accessor a1, a2;
823         if(i&1) utils::yield();
824         table.insert( a1, MyKey::make(1) );
825         utils::yield();
826         table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain
827         table.erase( a2 ); // if erase by key it would lead to deadlock for single thread
828     }
829 };
830 
831 struct FakeExclusive {
832     utils::SpinBarrier& barrier;
833     YourTable& table;
FakeExclusiveFakeExclusive834     FakeExclusive(utils::SpinBarrier& b, YourTable&t) : barrier(b), table(t) {}
operator ()FakeExclusive835     void operator()( std::size_t i ) const {
836         if(i) {
837             YourTable::const_accessor real_ca;
838             // const accessor on non-const table acquired as reader (shared)
839             CHECK(table.find(real_ca,MyKey::make(1)));
840             barrier.wait(); // item can be erased
841             std::this_thread::sleep_for(std::chrono::milliseconds(10)); // let it enter the erase
842             real_ca->second.value_of(); // check the state while holding accessor
843         } else {
844             YourTable::accessor fake_ca;
845             const YourTable &const_table = table;
846             // non-const accessor on const table acquired as reader (shared)
847             CHECK(const_table.find(fake_ca,MyKey::make(1)));
848             barrier.wait(); // readers acquired
849             // can mistakenly remove the item while other readers still refers to it
850             table.erase( fake_ca );
851         }
852     }
853 };
854 
855 using AtomicByte = std::atomic<unsigned char>;
856 
857 template<typename RangeType>
858 struct ParallelTraverseBody {
859     const size_t n;
860     AtomicByte* const array;
ParallelTraverseBodyParallelTraverseBody861     ParallelTraverseBody( AtomicByte array_[], size_t n_ ) :
862         n(n_),
863         array(array_)
864     {}
operator ()ParallelTraverseBody865     void operator()( const RangeType& range ) const {
866         for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) {
867             int k = i->first.value_of();
868             CHECK_FAST((0<=k && size_t(k)<n));
869             ++array[k];
870         }
871     }
872 };
873 
Check(AtomicByte array[],size_t n,size_t expected_size)874 void Check( AtomicByte array[], size_t n, size_t expected_size ) {
875     if( expected_size )
876         for( size_t k=0; k<n; ++k ) {
877             if( array[k] != int(UseKey(k)) ) {
878                 REPORT("array[%d]=%d != %d=UseKey(%d)\n",
879                        int(k), int(array[k]), int(UseKey(k)), int(k));
880                 CHECK(false);
881             }
882         }
883 }
884 
885 //! Test traversing the table with a parallel range
ParallelTraverseTable(test_table_type & table,size_t n,size_t expected_size)886 void ParallelTraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
887     INFO("testing parallel traversal\n");
888     CHECK(table.size()==expected_size);
889     AtomicByte* array = new AtomicByte[n];
890 
891     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
892     test_table_type::range_type r = table.range(10);
893     oneapi::tbb::parallel_for( r, ParallelTraverseBody<test_table_type::range_type>( array, n ));
894     Check( array, n, expected_size );
895 
896     const test_table_type& const_table = table;
897     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
898     test_table_type::const_range_type cr = const_table.range(10);
899     oneapi::tbb::parallel_for( cr, ParallelTraverseBody<test_table_type::const_range_type>( array, n ));
900     Check( array, n, expected_size );
901 
902     delete[] array;
903 }
904 
TestInsertFindErase(std::size_t nthread)905 void TestInsertFindErase( std::size_t nthread ) {
906     int n=250000;
907 
908     // compute m = number of unique keys
909     int m = 0;
910     for( int i=0; i<n; ++i )
911         m += UseKey(i);
912     {
913         test_allocator_type alloc;
914         test_allocator_type::init_counters();
915         CHECK(MyDataCount==0);
916         test_table_type table(alloc);
917         TraverseTable(table,n,0);
918         ParallelTraverseTable(table,n,0);
919 
920         for ( int i = 0; i < 2; ++i ) {
921             if ( i==0 )
922                 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread );
923             else
924                 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread );
925             CHECK(MyDataCount == m);
926             TraverseTable( table, n, m );
927             ParallelTraverseTable( table, n, m );
928 
929             DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread );
930             CHECK(MyDataCount == m);
931 
932             DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread );
933             CHECK(MyDataCount == m);
934 
935             EraseCount = 0;
936             DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread );
937             CHECK(EraseCount == m);
938             CHECK(MyDataCount == 0);
939             TraverseTable( table, n, 0 );
940 
941             table.clear();
942         }
943 
944         if( nthread > 1 ) {
945             YourTable ie_table;
946             for( int i=0; i<IE_SIZE; ++i )
947                 InsertEraseCount[i] = 0;
948             DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread);
949             for( int i=0; i<IE_SIZE; ++i )
950                 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i)));
951 
952             DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread);
953             utils::SpinBarrier barrier(nthread);
954             INFO("testing erase on fake exclusive accessor\n");
955             utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table));
956         }
957     }
958     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
959     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
960     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
961 }
962 
963 std::atomic<int> Counter;
964 
965 class AddToTable {
966     test_table_type& my_table;
967     const std::size_t my_nthread;
968     const int my_m;
969 public:
AddToTable(test_table_type & table,std::size_t nthread,int m)970     AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
operator ()(std::size_t) const971     void operator()( std::size_t ) const {
972         for( int i=0; i<my_m; ++i ) {
973             // Busy wait to synchronize threads
974             int j = 0;
975             while( Counter<i ) {
976                 if( ++j==1000000 ) {
977                     // If Counter<i after a million iterations, then we almost surely have
978                     // more logical threads than physical threads, and should yield in
979                     // order to let suspended logical threads make progress.
980                     j = 0;
981                     utils::yield();
982                 }
983             }
984             // Now all threads attempt to simultaneously insert a key.
985             int k;
986             {
987                 test_table_type::accessor a;
988                 MyKey key = MyKey::make(i);
989                 if( my_table.insert( a, key ) )
990                     a->second.set_value( 1 );
991                 else
992                     a->second.set_value( a->second.value_of()+1 );
993                 k = a->second.value_of();
994             }
995             if( std::size_t(k) == my_nthread )
996                 Counter=i+1;
997         }
998     }
999 };
1000 
1001 class RemoveFromTable {
1002     test_table_type& my_table;
1003     const int my_m;
1004 public:
RemoveFromTable(test_table_type & table,int m)1005     RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {}
operator ()(std::size_t) const1006     void operator()(std::size_t) const {
1007         for( int i=0; i<my_m; ++i ) {
1008             bool b;
1009             if(i&4) {
1010                 if(i&8) {
1011                     test_table_type::const_accessor a;
1012                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1013                 } else {
1014                     test_table_type::accessor a;
1015                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1016                 }
1017             } else
1018                 b = my_table.erase( MyKey::make(i) );
1019             if( b ) ++EraseCount;
1020         }
1021     }
1022 };
1023 
TestConcurrency(std::size_t nthread)1024 void TestConcurrency( std::size_t nthread ) {
1025     INFO("testing multiple insertions/deletions of same key with " << nthread << " threads");
1026     test_allocator_type::init_counters();
1027     {
1028         CHECK( MyDataCount==0);
1029         test_table_type table;
1030         const int m = 1000;
1031         Counter = 0;
1032         oneapi::tbb::tick_count t0 = oneapi::tbb::tick_count::now();
1033         utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) );
1034         REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" );
1035 
1036         EraseCount = 0;
1037         t0 = oneapi::tbb::tick_count::now();
1038         utils::NativeParallelFor( nthread, RemoveFromTable(table,m) );
1039         REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1040         REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken");
1041 
1042     }
1043     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
1044     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
1045     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
1046     REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1047 }
1048 
1049 template<typename Key>
1050 struct non_default_constructible_hash_compare : oneapi::tbb::detail::d1::tbb_hash_compare<Key> {
non_default_constructible_hash_comparenon_default_constructible_hash_compare1051     non_default_constructible_hash_compare() {
1052         REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument");
1053     }
1054 
non_default_constructible_hash_comparenon_default_constructible_hash_compare1055     non_default_constructible_hash_compare(int) {}
1056 };
1057 
TestHashCompareConstructors()1058 void TestHashCompareConstructors() {
1059     using key_type = int;
1060     using map_type = oneapi::tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>;
1061 
1062     non_default_constructible_hash_compare<key_type> compare(0);
1063     map_type::allocator_type allocator;
1064 
1065     map_type map1(compare);
1066     map_type map2(compare, allocator);
1067 
1068     map_type map3(1, compare);
1069     map_type map4(1, compare, allocator);
1070 
1071     std::vector<map_type::value_type> reference_vector;
1072     map_type map5(reference_vector.begin(), reference_vector.end(), compare);
1073     map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator);
1074 
1075     map_type map7({}, compare);
1076     map_type map8({}, compare, allocator);
1077 }
1078 
1079 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1080 template <typename T>
1081 struct debug_hash_compare : public oneapi::tbb::detail::d1::tbb_hash_compare<T> {};
1082 
1083 template <template <typename...> typename TMap>
TestDeductionGuides()1084 void TestDeductionGuides() {
1085     using Key = int;
1086     using Value = std::string;
1087 
1088     using ComplexType = std::pair<Key, Value>;
1089     using ComplexTypeConst = std::pair<const Key, Value>;
1090 
1091     using DefaultCompare = oneapi::tbb::detail::d1::tbb_hash_compare<Key>;
1092     using Compare = debug_hash_compare<Key>;
1093     using DefaultAllocator = oneapi::tbb::tbb_allocator<ComplexTypeConst>;
1094     using Allocator = std::allocator<ComplexTypeConst>;
1095 
1096     std::vector<ComplexType> v;
1097     auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") };
1098     Compare compare;
1099     Allocator allocator;
1100 
1101     // check TMap(InputIterator, InputIterator)
1102     TMap m1(v.begin(), v.end());
1103     static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1104 
1105     // check TMap(InputIterator, InputIterator, HashCompare)
1106     TMap m2(v.begin(), v.end(), compare);
1107     static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value);
1108 
1109     // check TMap(InputIterator, InputIterator, HashCompare, Allocator)
1110     TMap m3(v.begin(), v.end(), compare, allocator);
1111     static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value);
1112 
1113     // check TMap(InputIterator, InputIterator, Allocator)
1114     TMap m4(v.begin(), v.end(), allocator);
1115     static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1116 
1117     // check TMap(std::initializer_list)
1118     TMap m5(l);
1119     static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1120 
1121     // check TMap(std::initializer_list, HashCompare)
1122     TMap m6(l, compare);
1123     static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value);
1124 
1125     // check TMap(std::initializer_list, HashCompare, Allocator)
1126     TMap m7(l, compare, allocator);
1127     static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value);
1128 
1129     // check TMap(std::initializer_list, Allocator)
1130     TMap m8(l, allocator);
1131     static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1132 
1133     // check TMap(TMap &)
1134     TMap m9(m1);
1135     static_assert(std::is_same<decltype(m9), decltype(m1)>::value);
1136 
1137     // check TMap(TMap &, Allocator)
1138     TMap m10(m4, allocator);
1139     static_assert(std::is_same<decltype(m10), decltype(m4)>::value);
1140 
1141     // check TMap(TMap &&)
1142     TMap m11(std::move(m1));
1143     static_assert(std::is_same<decltype(m11), decltype(m1)>::value);
1144 
1145     // check TMap(TMap &&, Allocator)
1146     TMap m12(std::move(m4), allocator);
1147     static_assert(std::is_same<decltype(m12), decltype(m4)>::value);
1148 }
1149 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1150 
1151 template <typename CHMapType>
test_comparisons_basic()1152 void test_comparisons_basic() {
1153     using comparisons_testing::testEqualityComparisons;
1154     CHMapType c1, c2;
1155     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1156 
1157     c1.emplace(1, 1);
1158     testEqualityComparisons</*ExpectEqual = */false>(c1, c2);
1159 
1160     c2.emplace(1, 1);
1161     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1162 }
1163 
1164 template <typename TwoWayComparableContainerType>
test_two_way_comparable_chmap()1165 void test_two_way_comparable_chmap() {
1166     TwoWayComparableContainerType c1, c2;
1167     c1.emplace(1, 1);
1168     c2.emplace(1, 1);
1169     comparisons_testing::TwoWayComparable::reset();
1170     REQUIRE_MESSAGE(c1 == c2, "Incorrect operator == result");
1171     comparisons_testing::check_equality_comparison();
1172     REQUIRE_MESSAGE(!(c1 != c2), "Incorrect operator != result");
1173     comparisons_testing::check_equality_comparison();
1174 }
1175 
TestCHMapComparisons()1176 void TestCHMapComparisons() {
1177     using integral_container = oneapi::tbb::concurrent_hash_map<int, int>;
1178     using two_way_comparable_container = oneapi::tbb::concurrent_hash_map<comparisons_testing::TwoWayComparable,
1179                                                                           comparisons_testing::TwoWayComparable>;
1180 
1181     test_comparisons_basic<integral_container>();
1182     test_comparisons_basic<two_way_comparable_container>();
1183     test_two_way_comparable_chmap<two_way_comparable_container>();
1184 }
1185 
1186 template <typename Iterator, typename CHMapType>
TestCHMapIteratorComparisonsBasic(CHMapType & chmap)1187 void TestCHMapIteratorComparisonsBasic( CHMapType& chmap ) {
1188     REQUIRE_MESSAGE(!chmap.empty(), "Incorrect test setup");
1189     using namespace comparisons_testing;
1190     Iterator it1, it2;
1191     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1192     it1 = chmap.begin();
1193     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1194     it2 = chmap.begin();
1195     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1196     it2 = chmap.end();
1197     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1198 }
1199 
TestCHMapIteratorComparisons()1200 void TestCHMapIteratorComparisons() {
1201     using chmap_type = oneapi::tbb::concurrent_hash_map<int, int>;
1202     using value_type = typename chmap_type::value_type;
1203     chmap_type chmap = {value_type{1, 1}, value_type{2, 2}, value_type{3, 3}};
1204     TestCHMapIteratorComparisonsBasic<typename chmap_type::iterator>(chmap);
1205     const chmap_type& cchmap = chmap;
1206     TestCHMapIteratorComparisonsBasic<typename chmap_type::const_iterator>(cchmap);
1207 }
1208 
1209 template <bool IsConstructible>
1210 class HeterogeneousKey {
1211 public:
1212     static std::size_t heterogeneous_keys_count;
1213 
integer_key() const1214     int integer_key() const { return my_key; }
1215 
1216     template <bool I = IsConstructible, typename = typename std::enable_if<I>::type>
HeterogeneousKey(int key)1217     HeterogeneousKey(int key) : my_key(key) { ++heterogeneous_keys_count; }
1218 
1219     HeterogeneousKey(const HeterogeneousKey&) = delete;
1220     HeterogeneousKey& operator=(const HeterogeneousKey&) = delete;
1221 
reset()1222     static void reset() { heterogeneous_keys_count = 0; }
1223 
1224     struct construct_flag {};
1225 
HeterogeneousKey(construct_flag,int key)1226     HeterogeneousKey( construct_flag, int key ) : my_key(key) {}
1227 
1228 private:
1229     int my_key;
1230 }; // class HeterogeneousKey
1231 
1232 template <bool IsConstructible>
1233 std::size_t HeterogeneousKey<IsConstructible>::heterogeneous_keys_count = 0;
1234 
1235 struct HeterogeneousHashCompare {
1236     using is_transparent = void;
1237 
1238     template <bool IsConstructible>
hashHeterogeneousHashCompare1239     std::size_t hash( const HeterogeneousKey<IsConstructible>& key ) const {
1240         return my_hash_object(key.integer_key());
1241     }
1242 
hashHeterogeneousHashCompare1243     std::size_t hash( const int& key ) const {
1244         return my_hash_object(key);
1245     }
1246 
equalHeterogeneousHashCompare1247     bool equal( const int& key1, const int& key2 ) const {
1248         return key1 == key2;
1249     }
1250 
1251     template <bool IsConstructible>
equalHeterogeneousHashCompare1252     bool equal( const int& key1, const HeterogeneousKey<IsConstructible>& key2 ) const {
1253         return key1 == key2.integer_key();
1254     }
1255 
1256     template <bool IsConstructible>
equalHeterogeneousHashCompare1257     bool equal( const HeterogeneousKey<IsConstructible>& key1, const int& key2 ) const {
1258         return key1.integer_key() == key2;
1259     }
1260 
1261     template <bool IsConstructible>
equalHeterogeneousHashCompare1262     bool equal( const HeterogeneousKey<IsConstructible>& key1, const HeterogeneousKey<IsConstructible>& key2 ) const {
1263         return key1.integer_key() == key2.integer_key();
1264     }
1265 
1266     std::hash<int> my_hash_object;
1267 }; // struct HeterogeneousHashCompare
1268 
1269 class DefaultConstructibleValue {
1270 public:
DefaultConstructibleValue()1271     DefaultConstructibleValue() : my_i(default_value) {};
1272 
value() const1273     int value() const { return my_i; }
1274     static constexpr int default_value = -4242;
1275 private:
1276     int my_i;
1277 }; // class DefaultConstructibleValue
1278 
1279 constexpr int DefaultConstructibleValue::default_value;
1280 
test_heterogeneous_find()1281 void test_heterogeneous_find() {
1282     using key_type = HeterogeneousKey</*IsConstructible = */false>;
1283     using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
1284 
1285     chmap_type chmap;
1286     using const_accessor = typename chmap_type::const_accessor;
1287     using accessor = typename chmap_type::accessor;
1288     const_accessor cacc;
1289     accessor acc;
1290 
1291     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
1292 
1293     key_type key(key_type::construct_flag{}, 1);
1294     bool regular_result = chmap.find(cacc, key);
1295     bool heterogeneous_result = chmap.find(cacc, int(1));
1296 
1297     REQUIRE(!regular_result);
1298     REQUIRE_MESSAGE(regular_result == heterogeneous_result,
1299                     "Incorrect heterogeneous find result with const_accessor (no element)");
1300     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with const_accessor (no element)");
1301 
1302     regular_result = chmap.find(acc, key);
1303     heterogeneous_result = chmap.find(acc, int(1));
1304 
1305     REQUIRE(!regular_result);
1306     REQUIRE_MESSAGE(regular_result == heterogeneous_result,
1307                     "Incorrect heterogeneous find result with accessor (no element)");
1308     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with accessor (no element)");
1309 
1310     bool tmp_result = chmap.emplace(cacc, std::piecewise_construct,
1311                                     std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
1312     REQUIRE(tmp_result);
1313 
1314     regular_result = chmap.find(cacc, key);
1315     heterogeneous_result = chmap.find(cacc, int(1));
1316 
1317     REQUIRE(regular_result);
1318     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous find result with const_accessor (element exists)");
1319     REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor returned");
1320     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with const_accessor (element exists)");
1321     cacc.release();
1322 
1323     regular_result = chmap.find(acc, key);
1324     heterogeneous_result = chmap.find(acc, int(1));
1325 
1326     REQUIRE(regular_result);
1327     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous find result with accessor (element exists)");
1328     REQUIRE_MESSAGE(acc->first.integer_key() == 1, "Incorrect accessor returned");
1329     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with accessor (element exists)");
1330     key_type::reset();
1331 }
1332 
test_heterogeneous_count()1333 void test_heterogeneous_count() {
1334     using key_type = HeterogeneousKey</*IsConstructible = */false>;
1335     using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
1336 
1337     chmap_type chmap;
1338 
1339     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
1340     key_type key(key_type::construct_flag{}, 1);
1341 
1342     typename chmap_type::size_type regular_count = chmap.count(key);
1343     typename chmap_type::size_type heterogeneous_count = chmap.count(int(1));
1344 
1345     REQUIRE(regular_count == 0);
1346     REQUIRE_MESSAGE(regular_count == heterogeneous_count, "Incorrect heterogeneous count result (no element)");
1347     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during count call (no element)");
1348 
1349     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
1350 
1351     regular_count = chmap.count(key);
1352     heterogeneous_count = chmap.count(int(1));
1353 
1354     REQUIRE(regular_count == 1);
1355     REQUIRE_MESSAGE(regular_count == heterogeneous_count, "Incorrect heterogeneous count result (element exists)");
1356     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during count call (element exists)");
1357     key_type::reset();
1358 }
1359 
test_heterogeneous_equal_range()1360 void test_heterogeneous_equal_range() {
1361     using key_type = HeterogeneousKey</*IsConstructible = */false>;
1362     using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
1363 
1364     chmap_type chmap;
1365     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
1366 
1367     using iterator = typename chmap_type::iterator;
1368     using const_iterator = typename chmap_type::const_iterator;
1369     using result = std::pair<iterator, iterator>;
1370     using const_result = std::pair<const_iterator, const_iterator>;
1371     key_type key(key_type::construct_flag{}, 1);
1372 
1373     result regular_result = chmap.equal_range(key);
1374     result heterogeneous_result = chmap.equal_range(int(1));
1375 
1376     REQUIRE(regular_result.first == chmap.end());
1377     REQUIRE(regular_result.second == chmap.end());
1378     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous equal_range result (non const, no element)");
1379     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (non const, no element)");
1380 
1381     const chmap_type& cchmap = chmap;
1382 
1383     const_result regular_const_result = cchmap.equal_range(key);
1384     const_result heterogeneous_const_result = cchmap.equal_range(int(1));
1385 
1386     REQUIRE(regular_const_result.first == cchmap.end());
1387     REQUIRE(regular_const_result.second == cchmap.end());
1388     REQUIRE_MESSAGE(regular_const_result == heterogeneous_const_result,
1389                     "Incorrect heterogeneous equal_range result (const, no element)");
1390     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (const, no element)");
1391 
1392     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
1393 
1394     regular_result = chmap.equal_range(key);
1395     heterogeneous_result = chmap.equal_range(int(1));
1396 
1397     REQUIRE(regular_result.first != chmap.end());
1398     REQUIRE(regular_result.first->first.integer_key() == 1);
1399     REQUIRE(regular_result.second == chmap.end());
1400     REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous equal_range result (non const, element exists)");
1401     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (non const, element exists)");
1402 
1403     regular_const_result = cchmap.equal_range(key);
1404     heterogeneous_const_result = cchmap.equal_range(int(1));
1405     REQUIRE_MESSAGE(regular_const_result == heterogeneous_const_result,
1406                     "Incorrect heterogeneous equal_range result (const, element exists)");
1407     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (const, element exists)");
1408     key_type::reset();
1409 }
1410 
test_heterogeneous_insert()1411 void test_heterogeneous_insert() {
1412     using key_type = HeterogeneousKey</*IsConstructible = */true>;
1413     using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, DefaultConstructibleValue, HeterogeneousHashCompare>;
1414 
1415     chmap_type chmap;
1416     using const_accessor = typename chmap_type::const_accessor;
1417     using accessor = typename chmap_type::accessor;
1418     const_accessor cacc;
1419     accessor acc;
1420 
1421     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
1422 
1423     bool result = chmap.insert(cacc, int(1));
1424 
1425     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 1, "Only one heterogeneous key should be created");
1426     REQUIRE_MESSAGE(result, "Incorrect heterogeneous insert result (const_accessor)");
1427     REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor");
1428     REQUIRE_MESSAGE(cacc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
1429 
1430     result = chmap.insert(cacc, int(1));
1431 
1432     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 1, "No extra keys should be created");
1433     REQUIRE_MESSAGE(!result, "Incorrect heterogeneous insert result (const_accessor)");
1434     REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor");
1435     REQUIRE_MESSAGE(cacc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
1436 
1437     result = chmap.insert(acc, int(2));
1438 
1439     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 2, "Only one extra heterogeneous key should be created");
1440     REQUIRE_MESSAGE(result, "Incorrect heterogeneous insert result (accessor)");
1441     REQUIRE_MESSAGE(acc->first.integer_key() == 2, "Incorrect accessor");
1442     REQUIRE_MESSAGE(acc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
1443 
1444     result = chmap.insert(acc, int(2));
1445 
1446     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 2, "No extra keys should be created");
1447     REQUIRE_MESSAGE(!result, "Incorrect heterogeneous insert result (accessor)");
1448     REQUIRE_MESSAGE(acc->first.integer_key() == 2, "Incorrect accessor");
1449     REQUIRE_MESSAGE(acc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed");
1450 
1451     key_type::reset();
1452 }
1453 
test_heterogeneous_erase()1454 void test_heterogeneous_erase() {
1455     using key_type = HeterogeneousKey</*IsConstructible = */false>;
1456     using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>;
1457 
1458     chmap_type chmap;
1459 
1460     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup");
1461 
1462     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100));
1463     chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 2), std::forward_as_tuple(200));
1464 
1465     typename chmap_type::const_accessor cacc;
1466 
1467     REQUIRE(chmap.find(cacc, int(1)));
1468     REQUIRE(chmap.find(cacc, int(2)));
1469 
1470     cacc.release();
1471 
1472     bool result = chmap.erase(int(1));
1473     REQUIRE_MESSAGE(result, "Erasure should be successful");
1474     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "No extra keys should be created");
1475     REQUIRE_MESSAGE(!chmap.find(cacc, int(1)), "Element was not erased");
1476 
1477 
1478     result = chmap.erase(int(1));
1479     REQUIRE_MESSAGE(!result, "Erasure should fail");
1480     REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "No extra keys should be created");
1481     key_type::reset();
1482 }
1483 
test_heterogeneous_lookup()1484 void test_heterogeneous_lookup() {
1485     test_heterogeneous_find();
1486     test_heterogeneous_count();
1487     test_heterogeneous_equal_range();
1488 }
1489 
1490 //! Test construction with hash_compare
1491 //! \brief \ref interface \ref requirement
1492 TEST_CASE("testing construction with hash_compare") {
1493     TestHashCompareConstructors();
1494 }
1495 
1496 //! Test concurrent_hash_map member types
1497 //! \brief \ref interface \ref requirement
1498 TEST_CASE("test types"){
1499     test_member_types<oneapi::tbb::concurrent_hash_map>();
1500 }
1501 
1502 //! Test swap and clear operations
1503 //! \brief \ref interface \ref requirement
1504 TEST_CASE("test copy operations") {
1505     TestCopy();
1506 }
1507 
1508 //! Test rehash operation
1509 //! \brief \ref interface \ref requirement
1510 TEST_CASE("test rehash operation") {
1511     TestRehash();
1512 }
1513 
1514 //! Test assignment operation
1515 //! \brief \ref interface \ref requirement
1516 TEST_CASE("test assignment operation") {
1517     TestAssignment();
1518 }
1519 
1520 //! Test iterators and ranges
1521 //! \brief \ref interface \ref requirement
1522 TEST_CASE("test iterators and ranges") {
1523     TestIteratorsAndRanges();
1524 }
1525 
1526 //! Test work with initializer_list
1527 //! \brief \ref interface \ref requirement
1528 TEST_CASE("test work with initializer_list") {
1529     TestInitList();
1530 }
1531 
1532 #if TBB_USE_EXCEPTIONS
1533 //! Test exception safety
1534 //! \brief \ref requirement
1535 TEST_CASE("test exception safety") {
1536     TestExceptions();
1537 }
1538 
1539 //! Test exceptions safety guarantees for move constructor
1540 //! \brief \ref requirement
1541 TEST_CASE("test move support with exceptions") {
1542     move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>();
1543     move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>();
1544 }
1545 #endif
1546 
1547 //! Test move constructor
1548 //! \brief \ref interface \ref requirement
1549 TEST_CASE("testing move constructor"){
1550     move_support_tests::test_move_constructor<hash_map_traits>();
1551 }
1552 
1553 //! Test move assign operator
1554 //! \brief \ref interface \ref requirement
1555 TEST_CASE("testing move assign operator"){
1556     move_support_tests::test_move_assignment<hash_map_traits>();
1557 }
1558 
1559 //! Test insert and empace
1560 //! \brief \ref interface \ref requirement
1561 TEST_CASE("testing concurrent insert and emplace"){
1562     int n=250000;
1563     {
1564         DataStateTrackedTable table;
1565         DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 );
1566     }
1567     {
1568         DataStateTrackedTable table;
1569         DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 );
1570     }
1571 }
1572 
1573 //! Test allocator traits
1574 //! \brief \ref requirement
1575 TEST_CASE("testing allocator traits") {
1576     test_allocator_traits_support<hash_map_traits>();
1577 }
1578 
1579 //! Test concurrent operations
1580 //! \brief \ref requirement
1581 TEST_CASE("testing concurrency"){
1582     for (std::size_t p = 1; p <= 4; ++p) {
1583         oneapi::tbb::global_control limit(oneapi::tbb::global_control::max_allowed_parallelism, p);
1584         TestInsertFindErase(p);
1585         TestConcurrency(p);
1586     }
1587 }
1588 
1589 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1590 //! Test deduction guides
1591 //! \brief \ref interface
1592 TEST_CASE("testing deduction guides") {
1593     TestDeductionGuides<oneapi::tbb::concurrent_hash_map>();
1594 }
1595 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1596 
1597 //! \brief \ref interface \ref requirement
1598 TEST_CASE("concurrent_hash_map comparisons") {
1599     TestCHMapComparisons();
1600 }
1601 
1602 //! \brief \ref interface \ref requirement
1603 TEST_CASE("concurrent_hash_map iterator comparisons") {
1604     TestCHMapIteratorComparisons();
1605 }
1606 
1607 //! \brief \ref interface \ref requirement
1608 TEST_CASE("test concurrent_hash_map heterogeneous lookup") {
1609     test_heterogeneous_lookup();
1610 }
1611 
1612 //! \brief \ref interface \ref requirement
1613 TEST_CASE("test concurrent_hash_map heterogeneous insert") {
1614     test_heterogeneous_insert();
1615 }
1616 
1617 //! \brief \ref interface \ref requirement
1618 TEST_CASE("test concurrent_hash_map heterogeneous erase") {
1619     test_heterogeneous_erase();
1620 }
1621