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