Compare commits

..

323 Commits

Author SHA1 Message Date
joaquintides 8149197fa8 replaced our spinlock with tbb's 2023-06-12 19:27:39 +02:00
joaquintides c2484f05f0 avoided old GCC/Clang hiccup with {} for aggregate copy construction 2023-06-09 21:31:30 +02:00
joaquintides f7e2132bdb Merge branch 'feature/cfoa' of https://github.com/boostorg/unordered into feature/cfoa 2023-06-09 21:20:36 +02:00
joaquintides 07a8b2b14d stylistic 2023-06-09 08:55:25 -07:00
joaquintides 2eac49f0ed added copy ctor for cache_aligned_size_control 2023-06-09 08:55:25 -07:00
joaquintides 6a8d7857e2 fixed unqualified swap call 2023-06-09 08:55:25 -07:00
joaquintides ef34ec5e87 added missing #include 2023-06-09 08:55:25 -07:00
joaquintides 518a5d2e1e refactored padding into a clean design 2023-06-09 08:55:25 -07:00
joaquintides 3ea8a768ca added padding between and around ml and size_ 2023-06-09 08:55:25 -07:00
joaquintides 4b877d363a updated concurrent_flat_map benchmarks 2023-06-09 08:55:25 -07:00
Christian Mazakas 2395db7bb9 Cleanup docs 2023-06-09 08:55:25 -07:00
joaquintides 7327053eea updated concurrent_flat_map benchmarks 2023-06-08 09:34:05 +02:00
Christian Mazakas af19d99b70 Clean up CI to pre-debugging state 2023-06-07 10:05:19 -07:00
joaquintides 64f9370fff implemented cacheline alignment without extended alignas 2023-06-07 11:30:52 +02:00
joaquintides ca193de243 added missing #include 2023-06-07 11:28:00 +02:00
Christian Mazakas 2914925b4d Explicitly disable ccache in GHA runner file 2023-06-06 09:14:00 -07:00
Christian Mazakas bc37851725 Remove usage of ccache from problematic gcc-12 sanitizer actions 2023-06-06 08:51:59 -07:00
Christian Mazakas f89a31849c Fix workaround for cfoa_fwd_tests 2023-06-06 08:27:55 -07:00
Christian Mazakas ec945810eb Remove problematic test from clang-3.7 runner 2023-06-05 15:56:06 -07:00
Christian Mazakas 68ddaba57a Update cfoa's fwd header to be in line with the others 2023-06-05 15:47:41 -07:00
Christian Mazakas c48c2afc56 Add gcc-13 and Ubuntu 23.04 to Drone CI 2023-06-05 14:24:38 -07:00
Christian Mazakas 01a5dd0dd1 Add concurrent_flat_map to link tests for foa 2023-06-05 14:19:18 -07:00
Christian Mazakas 300a8e61bd Add ubsan + asan jobs to gcc-12 2023-06-05 11:16:34 -07:00
joaquintides 1d2be664a0 added boost::concurrent_flat_map benchmarks 2023-06-05 20:08:32 +02:00
Christian Mazakas e6b1ef9e1e Add ubsan gcc-12 runners to drone 2023-06-05 09:32:11 -07:00
Christian Mazakas 4efb55146a Update gcc used for code coverage collection 2023-06-02 15:08:29 -07:00
Christian Mazakas 8877d21237 Replace dependency on SmartPtr with primitives in Core 2023-06-02 14:14:18 -07:00
Christian Mazakas 44c50cd2ea Resolve potential ambiguities during insertion by introducing a member function template 2023-06-01 14:19:14 -07:00
Christian Mazakas e7c1e1a56e Clean up raii count checkers to avoid extraneous assertions 2023-06-01 14:18:54 -07:00
Christian Mazakas 9a22f8fbee Add missing dependency on SmartPtr 2023-05-31 09:14:44 -07:00
Christian Mazakas 51520de04b Add allocator using fancy pointers to insert_tests 2023-05-31 07:30:37 -07:00
Christian Mazakas 6e0f76f4c2 Add missing FindThreads to CML 2023-05-30 14:52:45 -07:00
Christian Mazakas 06aa4b5c19 Cleanup test CML 2023-05-30 13:24:21 -07:00
Christian Mazakas 332540c857 Attempt to fix CMake tests 2023-05-30 12:05:10 -07:00
Christian Mazakas fdedb6c957 Add merge exceptions tests for cfoa 2023-05-30 09:19:50 -07:00
joaquintides 7aaa2e9452 polished BOOST_UNORDERED_PREFETCH[_ELEMENTS] 2023-05-28 19:09:14 +02:00
Christian Mazakas 950e640fcf Update concurrent_table to use macro-based prefetching 2023-05-26 21:06:01 -07:00
Christian Mazakas 7874625c08 Replace prefetch_elements() with macro so builtins aren't optimized away by DSE 2023-05-26 20:56:52 -07:00
Christian Mazakas 2ab4225473 Add workaround for gcc-12 and above where the prefetch call is ignored 2023-05-26 08:16:02 -07:00
Christian Mazakas c5df4ec069 Remove unused test 2023-05-25 08:41:26 -07:00
Christian Mazakas 55d79204be Add exceptions tests for cfoa assign ops 2023-05-24 15:23:09 -07:00
Christian Mazakas f5d5299b88 Flesh out constructor exception tests 2023-05-24 11:06:45 -07:00
joaquintides 253a9bccf6 fixed leak in throwing allocator-extended move ctor 2023-05-24 09:19:31 +02:00
Christian Mazakas 61f11a58ee Push up failing test case for code review purposes 2023-05-23 15:28:14 -07:00
Christian Mazakas e78dc311e3 Clean up erase tests 2023-05-23 08:43:10 -07:00
Christian Mazakas a5748c2e8b Add initial container population to insert iterator range tests 2023-05-22 15:24:36 -07:00
Christian Mazakas 146c5cb6be Clean up exception tests 2023-05-22 14:19:21 -07:00
Christian Mazakas c63a88032d Loosen restrictions on erase exceptions tests to accomodate runs where there are no successful erasures 2023-05-22 12:10:10 -07:00
Christian Mazakas 3ad164267a Update duration of erase operations to trigger successful erasures when only 2 threads are available 2023-05-22 11:37:07 -07:00
Christian Mazakas a9203ed93c Clean up erase_tests 2023-05-22 10:17:36 -07:00
Christian Mazakas 16550ded0c Add exceptional erase tests 2023-05-22 10:17:36 -07:00
joaquintides b4c75abca9 typo 2023-05-22 09:49:00 +02:00
joaquintides a696bdecf6 editorial 2023-05-21 13:10:46 +02:00
joaquintides 8865a940fc editorial 2023-05-21 12:55:23 +02:00
joaquintides 2a28698c8c editorial 2023-05-21 12:43:45 +02:00
joaquintides 96f5983f88 fixed initializer_list insert requirements 2023-05-21 12:39:02 +02:00
joaquintides ddb1148a31 reformulated static member initialization to appease VS2015 2023-05-20 12:16:30 +02:00
Christian Mazakas ffcae204ee Add insert_exception_tests 2023-05-19 12:33:23 -07:00
joaquintides f28527c4d8 removed double separating line 2023-05-19 11:28:08 +02:00
joaquintides 528f7d4b12 title cased some sections 2023-05-19 11:17:56 +02:00
joaquintides a140de4254 typos/editorial 2023-05-19 10:51:00 +02:00
Christian Mazakas f1bc948be8 Update table formatting in intro to use monospaced font 2023-05-18 13:14:58 -07:00
joaquintides 3d640ac032 refactored to modernize and improve flow 2023-05-18 20:18:58 +02:00
Christian Mazakas ff10b287e2 Remove unreliable check from swap_tests 2023-05-17 13:36:56 -07:00
Christian Mazakas 4a416501c8 Fix misuse of ctad macro 2023-05-17 10:14:45 -07:00
Christian Mazakas bf06fa97e3 Add deduction guides 2023-05-17 09:38:29 -07:00
Christian Mazakas fcf6fee0f6 Make usage of forceinline consistent with the underlying concurrent_table 2023-05-16 15:42:47 -07:00
Christian Mazakas 8ddfc8ec7a Update execution policies to accept by forwarding reference 2023-05-16 14:33:41 -07:00
Christian Mazakas 5f249bc681 Add fwd header 2023-05-16 13:31:35 -07:00
Christian Mazakas dbd1a929e6 Remove unnenecessary spinning 2023-05-16 12:34:50 -07:00
Christian Mazakas 32ff2f145e Add initial draft of equality tests 2023-05-16 11:55:56 -07:00
Christian Mazakas 63026fd320 Clean up tests to avoid needless yields and extraneous spurious wakeups 2023-05-16 09:19:43 -07:00
Christian Mazakas 6295c7f0d4 Add free function erase_if() 2023-05-15 13:40:33 -07:00
Christian Mazakas c3879e238d Add free function swap() 2023-05-15 07:54:19 -07:00
joaquintides 9260bff8f8 editorial 2023-05-15 10:20:45 +02:00
joaquintides f48fc70f4b typos/editorial 2023-05-14 11:13:48 +02:00
joaquintides 48f703132e added implementation description for cfoa 2023-05-13 19:29:41 +02:00
joaquintides 69ee0039e0 added implementation rationale for concurrent hashmap 2023-05-13 19:29:08 +02:00
joaquintides add01e2dfd added compliance section for concurrent hashmap 2023-05-13 19:28:43 +02:00
joaquintides 4b4db3dfb3 fixed links 2023-05-13 10:00:35 +02:00
joaquintides 81480feeb4 fixed regression at unprotected_emplace 2023-05-12 12:45:31 +02:00
joaquintides d615a08f76 made operator [==|!=] templated again to accommodate fwd declarations 2023-05-12 12:33:27 +02:00
joaquintides dacc1c8234 made operator[==|!=] non-templated 2023-05-12 11:41:50 +02:00
joaquintides 511e2b3272 refactored to provide equality comparison from table_core 2023-05-12 11:24:20 +02:00
Christian Mazakas bcf5d0cf13 Attempt to disable extraneous runs on CI 2023-05-11 08:39:29 -07:00
Christian Mazakas 80a1904d92 Fix call_count check in rehash_tests 2023-05-11 08:39:16 -07:00
Christian Mazakas 21afc69894 Add initial tests for rehash(), reserve() 2023-05-10 13:22:02 -07:00
Christian Mazakas c90b72a643 Squelch gcc self-move warning for version 13 2023-05-09 14:00:14 -07:00
Christian Mazakas c2c34f96a3 Improve robustness of merge_tests to schedule merges without spurious wakeups and in a wider stride of insertions 2023-05-09 14:00:14 -07:00
joaquintides 69ba1c7c00 editorial 2023-05-09 19:53:56 +02:00
joaquintides ba25041fc8 added tutorial on boost::concurrent_flat_map 2023-05-08 18:37:36 +02:00
joaquintides 02197674f4 prevented VS C4800 warning 2023-05-06 12:44:07 +02:00
Christian Mazakas f0fe62d6ab Add count(), contains() 2023-05-05 15:41:23 -07:00
Christian Mazakas 53328766b9 Return size_type instead of size_t 2023-05-05 15:41:08 -07:00
Christian Mazakas 5b775345ba Clean up concurrent_table's merge impl 2023-05-05 11:47:20 -07:00
Christian Mazakas 99b0868283 Add initial impl of merge() 2023-05-05 10:20:52 -07:00
joaquintides 1c98a4a8f1 changed all titles to Title Casing 2023-05-05 18:56:33 +02:00
joaquintides bf73366117 typo 2023-05-05 17:55:53 +02:00
joaquintides 719394c522 Split important info int separate paragraphs 2023-05-05 17:55:02 +02:00
joaquintides 03fccc1947 refined concurrency requirements and guarantees 2023-05-05 17:37:20 +02:00
joaquintides 814264082f fixed BNF syntax 2023-05-04 19:19:37 +02:00
joaquintides 70e3dc4628 Changed the return type of iterator/initializer_list insert[_or_[c]visit] and merge to size_type 2023-05-04 19:16:39 +02:00
joaquintides b72dbef1a9 added equality comparison to reference 2023-05-04 18:51:03 +02:00
joaquintides 26924c73b9 fixed space reservation in concurrent_table::operator=(std::initializer_list) 2023-05-04 18:09:28 +02:00
Christian Mazakas 23e720a968 Split up Drone jobs even further due to extended runtimes 2023-05-03 15:32:06 -07:00
Christian Mazakas 4fb7751b55 Add missing #include 2023-05-03 11:37:32 -07:00
Christian Mazakas 3c0fb0fa1b Attempt to fix flaky CI 2023-05-03 11:37:32 -07:00
Christian Mazakas 3fe0807ae9 Add test that intermixes insertion and visitation
Attempt to test the happens-before and synchronizes-with relationship, looking for potential bugs on weakly-ordered models
2023-05-03 11:37:32 -07:00
joaquintides dfb4f2a28a added reference for boost::concurrent_flat_map 2023-05-03 17:53:13 +02:00
Christian Mazakas cc4cfc7ef2 Fix bug in swap_test iteration logic that caused early termination of the swap loop 2023-05-02 15:57:16 -07:00
Christian Mazakas a9bf367d6e Test if being the kind of program that calls yield() pays dividends for flaky CI failures 2023-05-02 15:44:21 -07:00
Christian Mazakas 2ea0dbf30e Add impl of member function swap() 2023-05-02 13:44:27 -07:00
Christian Mazakas 40c4d456f3 Clean up for CI 2023-05-01 15:21:25 -07:00
Christian Mazakas 011b7a5969 Add initial impl of clear 2023-05-01 11:58:58 -07:00
Christian Mazakas c52ad849ea Add policy check that excludes unsequenced policies
It's technically UB for the callable in an unsequenced policy to acquire a lock so we add static_assert()s to catch potential user error.
2023-05-01 11:58:58 -07:00
joaquintides 615ce1e9b6 refactored unprotected_rehash_if_full out 2023-04-29 11:35:11 +02:00
Christian Mazakas bee9a3cb1a Split Drone jobs to help with CI timeouts and load 2023-04-28 14:10:58 -07:00
Christian Mazakas 081932221f Attempt to fix flaky assign_tests 2023-04-28 13:42:28 -07:00
Christian Mazakas 135c9586af Add fuzzy test mixing copy-assignment with insertion 2023-04-27 15:23:21 -07:00
Christian Mazakas 187fd3e71e Implement initializer_list assignment 2023-04-27 12:00:42 -07:00
Christian Mazakas 0959df1896 Flesh out move assignment tests 2023-04-27 09:30:49 -07:00
Christian Mazakas 7833a8359d Use Core's allocator access to handle allocator_traits not having uniform support in early C++11 compilers 2023-04-26 13:58:20 -07:00
Christian Mazakas 212c6a1e4d Add prototype of move assignment 2023-04-26 12:54:04 -07:00
Christian Mazakas d7acb7e8b8 Fix capturing in latch_tests 2023-04-26 12:54:04 -07:00
joaquintides 0bc4f2c4b9 refactored foa::concurrent_table::merge internals 2023-04-26 13:39:38 +02:00
Christian Mazakas 2b612ed120 Flesh out assign_tests 2023-04-25 13:14:08 -07:00
Christian Mazakas a7c15e86fc Update num_threads to use the concurrent hint from the stdlib 2023-04-25 12:18:15 -07:00
joaquintides 4c117ab20a made merge blocking 2023-04-25 15:53:18 +02:00
Christian Mazakas e9c6a0fef5 Add polyfill implementation of std::latch 2023-04-24 13:29:35 -07:00
Christian Mazakas ac216a93c8 Add tests back in 2023-04-24 13:29:07 -07:00
joaquintides e49fef5f9a commented out all tests except those for cfoa 2023-04-23 17:27:50 +02:00
Christian Mazakas 26ab9ff584 Add prototype copy assignment operator 2023-04-21 15:56:22 -07:00
Christian Mazakas 1e92d9f545 Refactor stateful_allocator to test helpers 2023-04-21 13:33:18 -07:00
Christian Mazakas 2cf72093b1 Update test to spin until the container is non-empty 2023-04-21 10:44:06 -07:00
Christian Mazakas 88d4d64edf Add last remaining constructors 2023-04-21 10:33:21 -07:00
joaquintides 7f7e577e77 polished description 2023-04-21 09:13:28 +02:00
Christian Mazakas 80d7203d78 Add more iterator constructor overloads 2023-04-20 15:35:04 -07:00
Christian Mazakas 5dfed4deec Update thread_runner to block on hand-rolled barrier, add yield points to hash, key_equal 2023-04-20 14:34:01 -07:00
Christian Mazakas e08f9f11a1 Add more constructors 2023-04-20 12:58:29 -07:00
Christian Mazakas c304284773 Update tests to use condition variables for work synchronization instead of sleeps 2023-04-20 12:58:29 -07:00
joaquintides 0e8affcc07 [skip ci] editorial 2023-04-20 20:40:10 +02:00
joaquintides ba00dbd65a Merge branch 'feature/cfoa' of https://github.com/boostorg/unordered into feature/cfoa 2023-04-20 18:50:29 +02:00
joaquintides c214fb44a3 completed internal code documentation 2023-04-20 18:49:42 +02:00
Christian Mazakas c704788718 Fix flaky test by starting insertion thread earlier 2023-04-20 09:25:33 -07:00
joaquintides a91efeb237 refactored size_/ml swap 2023-04-20 17:29:21 +02:00
Christian Mazakas 7812b26d3a Add allocator aware move constructors 2023-04-19 15:18:53 -07:00
Christian Mazakas 8bd07e17c3 Add forced inlining to keep msvc benchmarks on par with raw concurrent_table 2023-04-19 10:52:41 -07:00
Christian Mazakas b3c0368ab5 Clean up typedef names to avoid conflicts 2023-04-19 09:35:47 -07:00
Christian Mazakas 37edc392a5 Add allocator-aware copy constructor 2023-04-19 09:35:38 -07:00
Christian Mazakas fb403bc233 Add explicit allocator constructor 2023-04-18 15:36:12 -07:00
Christian Mazakas 910b8de697 Add iterator range + allocator constructor, continue to clean up constructor_tests 2023-04-18 15:11:53 -07:00
Christian Mazakas 3aff995ae0 Clean up constructor_tests 2023-04-18 13:38:24 -07:00
Christian Mazakas 68c018fda6 Clean up usage of decltype to help msvc-14.3 2023-04-18 12:30:34 -07:00
Christian Mazakas a9ad06139e Add move constructor 2023-04-18 12:00:08 -07:00
joaquintides 9a0e5e9ea8 dispensed with std::array 2023-04-15 12:13:40 +02:00
joaquintides b1a52d0236 explicitly declared guards' copy ctors as it prevents VS from doing spurious copies (!) 2023-04-15 09:51:01 +02:00
joaquintides 1723358298 refactored to deICE Clang<=3.8 and for clarity 2023-04-14 10:48:26 +02:00
Christian Mazakas 6bf0e93a41 Remove size check as the insertion thread may not have started by the time the check happens 2023-04-13 13:30:56 -07:00
joaquintides 0e5ef25a69 added workaround for GCC<=5 having problems with return {...} syntax 2023-04-13 19:31:20 +02:00
joaquintides ab84a922cf added workaround for lack of std::is_trivially_copy_assignable in GCC<5.0 2023-04-13 13:00:29 +02:00
Christian Mazakas d8bc38f154 Clean up tag dispatch for group_type copy assignment 2023-04-12 14:41:36 -07:00
Christian Mazakas ecd4a82ed5 Add test for select_on_container_copy_construction Allocator requirement 2023-04-12 13:58:01 -07:00
Christian Mazakas 0898219edc Test empty visitation 2023-04-12 12:23:16 -07:00
Christian Mazakas d9b62d24dc Begin work on constructors 2023-04-12 12:22:59 -07:00
Christian Mazakas b771278813 Fix copy assignment warning in gcc 2023-04-12 12:22:38 -07:00
joaquintides 2226a7238d adjusted arg constness in erase and erase_if 2023-04-11 16:32:47 +02:00
joaquintides 00c6a93a56 restored invocability static asserts 2023-04-10 11:03:11 +02:00
joaquintides 8ea1cbbd64 s/BOOST_ASSERT/BOOST_TEST to avoid unused var warnings 2023-04-10 10:17:05 +02:00
joaquintides 81a47ca88d updated more tests affected by change in position of f 2023-04-10 09:05:19 +02:00
joaquintides c54151769f updated insert_or_assign impls to f-last interface 2023-04-09 21:56:12 +02:00
joaquintides e73d5ecdd1 updated tests affected by change in position of f 2023-04-09 20:41:15 +02:00
joaquintides 8a4e987030 made f the last argument in all *_or_[c]visit functions 2023-04-09 20:20:55 +02:00
Christian Mazakas 8544d9f3c8 Clean up test/Jamfile 2023-04-07 12:40:06 -07:00
Christian Mazakas ed80cb14e2 Add visit_all() 2023-04-07 12:39:39 -07:00
Christian Mazakas 91eb2ddbd1 Fix sign comparison warning 2023-04-07 12:39:13 -07:00
Christian Mazakas 3bd28dc3d7 Implement basic version of single element visitation 2023-04-06 13:42:57 -07:00
Christian Mazakas e4d2da40f2 Add missing static_assert()s 2023-04-06 09:36:08 -07:00
Christian Mazakas a004e71dd0 Implement emplace, emplace_or_[c]visit 2023-04-05 13:21:18 -07:00
Christian Mazakas ddcab1c171 Add impl of try_emplace, try_emplace_or_[c]visit 2023-04-04 15:16:52 -07:00
Christian Mazakas e3cbf03f47 Commit missing changes to erase_if() 2023-04-03 14:11:51 -07:00
Christian Mazakas f8fbbc3b76 Add ExecutionPolicy overload of erase_if 2023-04-03 13:15:15 -07:00
Christian Mazakas 52bf2bf7a2 Add insert_or_cvisit() overloads 2023-04-03 08:14:21 -07:00
Christian Mazakas cf39fc4c38 Implement transparent erase_if, unary erase_if 2023-04-03 08:14:21 -07:00
Christian Mazakas 1309361a02 Remove erroneous move() calls 2023-04-03 08:14:21 -07:00
joaquintides 946491489e added workaround for lack of std::is_trivially_constructible in GCC<5.0 2023-04-01 12:06:39 +02:00
joaquintides 19202d2a8d avoided memset-related GCC warning 2023-04-01 09:09:22 +02:00
joaquintides 3be466966d avoided copy elision for the initialization of an atomic 2023-03-31 19:46:59 +02:00
joaquintides d22787c218 use memset for group array initialization only when permissible 2023-03-31 19:36:31 +02:00
joaquintides b86dee9a18 removed BOOST_UNORDERED_EMBEDDED_GROUP_ACCESS support 2023-03-31 18:52:00 +02:00
joaquintides 863984a7c8 fixed erase_if(x,f) return value calculation 2023-03-31 17:28:21 +02:00
joaquintides e9b3ad4a5f reverted 2023-03-31 13:07:02 +02:00
joaquintides e00d700457 experiment with unaligned embedded group 2023-03-31 11:32:29 +02:00
Christian Mazakas 53e20a2a1b Add transparent impl of erase() 2023-03-30 12:41:31 -07:00
Christian Mazakas af4cdf8fab Shorten "transparent" to "transp" 2023-03-30 12:41:18 -07:00
Christian Mazakas 7003e91d44 Harden erase() tests 2023-03-30 11:43:12 -07:00
joaquintides e86bb5cce2 stylistic 2023-03-30 14:50:19 +02:00
joaquintides 6a4728add0 removed implementation of scoped_bilock copy ctor 2023-03-30 12:39:10 +02:00
joaquintides f02afbc815 simplified scoped_bilock impl 2023-03-30 12:13:58 +02:00
joaquintides 6399dfd1e4 made erase_if returned count exact 2023-03-30 10:08:38 +02:00
Christian Mazakas f468fb77e0 Hopefully fix sign-conversion warnings 2023-03-29 15:55:22 -07:00
Christian Mazakas 5e316ebc88 Add initial draft of erase() 2023-03-29 15:00:54 -07:00
Christian Mazakas 0c90585511 Refactor myriad test helpers into separate file 2023-03-29 15:00:32 -07:00
Christian Mazakas 13b4fd7133 Fix sign-compare warnings 2023-03-29 12:16:09 -07:00
joaquintides 27b4c62bd2 fixed initialization of concurrent_table::thread:counter 2023-03-29 20:35:48 +02:00
joaquintides 846de7ca82 added cvisit overloads to concurrent_table 2023-03-29 19:17:57 +02:00
joaquintides 62cf58d1ca sprinkled some inlines 2023-03-29 18:29:15 +02:00
joaquintides 4482031329 made concurrent_table::thread_counter static 2023-03-29 16:45:15 +02:00
Christian Mazakas 7b1af37b9c Update RAII counters to use unsigned integers instead of signed 2023-03-28 14:37:52 -07:00
Christian Mazakas 1c48f665ea Add alias target for foa container tests 2023-03-28 14:30:29 -07:00
Christian Mazakas e4072747bb Implement insert_or_visit() 2023-03-28 14:28:42 -07:00
Christian Mazakas ce674c8007 Add transparent insert_or_assign 2023-03-27 15:06:28 -07:00
joaquintides 45ebc1e555 fixed call ambiguities in insert_or_visit 2023-03-27 13:39:14 +02:00
joaquintides 02c3bd4072 stylistic 2023-03-27 11:32:07 +02:00
joaquintides 5f4172c13f refactored visitation an const-protection of args to visitation functions 2023-03-27 10:17:25 +02:00
joaquintides 40c5c6907a fixed const-casting in erase_if(foa::table&,...) 2023-03-25 18:50:25 +01:00
joaquintides 8dbe380d6c changed predicate arg to an lvalue ref to avoid ADL ambiguities 2023-03-25 17:37:02 +01:00
joaquintides 37ff9de0db fixed remaining errors in erase_if(foa::table&,...) 2023-03-25 17:35:37 +01:00
joaquintides ae9f35073b avoided sign conversion warning 2023-03-25 17:17:00 +01:00
joaquintides a85df27d4c fixed previous commit 2023-03-25 12:47:19 +01:00
joaquintides 88c70b885d fixed previous commit 2023-03-25 12:37:10 +01:00
joaquintides 240f5f94b1 moved erase_if_impl from foa::table_core into foa::table 2023-03-25 12:31:49 +01:00
joaquintides 7415721f7d completed shared/exclusive visitation 2023-03-25 12:28:44 +01:00
Christian Mazakas 8b5539756b Partially implement insert_or_assign() 2023-03-24 14:35:01 -07:00
joaquintides 49ac1035bb (partially) implemented shared/exclusive visitation 2023-03-24 19:49:59 +01:00
joaquintides d085c40541 relaxed all ops of atomic_integral 2023-03-24 18:37:53 +01:00
Christian Mazakas 99d5b9b1bb Avoid unsupported typedef on earlier compilers 2023-03-24 08:57:51 -07:00
Christian Mazakas ad86f9211b Flesh out tests for insertion 2023-03-23 10:59:50 -07:00
Christian Mazakas d9515fdc2f Add insert(initializer_list) 2023-03-23 10:59:50 -07:00
Christian Mazakas eedbc6bcf7 Add missing stdlib= to drone.sh 2023-03-23 10:59:50 -07:00
joaquintides 297fb2e8c4 abode by TSan on Neon 2023-03-23 09:41:36 +01:00
Christian Mazakas 99eab2dfb1 Fix missing quotes in jsonnet file 2023-03-22 15:14:16 -07:00
Christian Mazakas 1070c9b69e Attempt to add tsan to Drone CI 2023-03-22 15:11:55 -07:00
Christian Mazakas 9e6b5a7e43 Add tsan OSX job 2023-03-22 15:07:36 -07:00
Christian Mazakas e1f11e840c update clang tsan job to use libc++ 2023-03-22 13:55:42 -07:00
Christian Mazakas f2d4f1968b Explicitly add B2_TARGETS to script invocation 2023-03-22 12:49:05 -07:00
joaquintides 4a7116b996 partially restored prior commit to nail down problem with Clang x86 2023-03-22 20:16:09 +01:00
joaquintides 41584e73d9 partially reverted prior commit to nail down problem with Clang x86 2023-03-22 20:09:53 +01:00
joaquintides 52453cb5a4 Merge branch 'feature/cfoa' of https://github.com/boostorg/unordered into feature/cfoa 2023-03-22 19:25:10 +01:00
joaquintides 44b918896d tried relaxing atomic operations 2023-03-22 19:24:46 +01:00
Christian Mazakas ff356ac083 Fix typo in GHA ci.yml 2023-03-22 11:20:03 -07:00
Christian Mazakas 3064801a89 Attempt to add tsan to GHA 2023-03-22 11:19:13 -07:00
Christian Mazakas e9d9f19e76 Flesh out insert_tests 2023-03-22 10:47:01 -07:00
Christian Mazakas 9b85c38e37 Add rehash, add missing move in insert() 2023-03-22 10:46:19 -07:00
joaquintides 0b2829c793 hardened TSan detection 2023-03-21 20:03:35 +01:00
joaquintides a9c2d1daa5 abode by TSan on SSE2 2023-03-21 19:41:55 +01:00
joaquintides d29815b3a8 group-synchronized earlier in unprotected_norehash_emplace_or_visit 2023-03-21 14:47:43 +01:00
joaquintides 7ed7878278 fixed 3df435d4d3 fix 2023-03-21 11:50:21 +01:00
Christian Mazakas 4c927cd2f2 Implement myriad insert() overloads, get first threaded test working 2023-03-20 16:00:54 -07:00
joaquintides 2fae05ed31 made concurrent_table use table_core's automatic capacity growth formula 2023-03-20 12:43:57 -07:00
joaquintides 41abb96d83 avoided sign conversion warning 2023-03-20 12:43:57 -07:00
joaquintides 63f51e7c41 fixed concurrent_table_arrays::delete_ 2023-03-20 12:43:57 -07:00
joaquintides ba4f321934 removed unused typedef 2023-03-20 12:43:57 -07:00
joaquintides 68563fab72 removed unused typedef 2023-03-20 12:43:57 -07:00
joaquintides d8e9503869 made table_arrays non-aggregate for the benefit of older compilers 2023-03-20 12:43:57 -07:00
joaquintides 86e998cbc7 fixed UB in dummy_group_accesses 2023-03-20 12:43:57 -07:00
joaquintides ce722e4d56 avoided shadowing 2023-03-20 12:43:57 -07:00
joaquintides 77eaab4803 provided concurrent_table_arrays ctor to satisfy older compilers 2023-03-20 12:43:57 -07:00
joaquintides 96562f4c3b hardened non-embedded group_access 2023-03-20 12:43:57 -07:00
joaquintides d66ceaf7fe avoided std::atomic_* aliases 2023-03-20 12:43:57 -07:00
joaquintides 2e44769247 added preliminary version of non-embedded group_access 2023-03-20 12:43:57 -07:00
joaquintides e64fd45760 removed inefficient group15::match_really_occupied 2023-03-20 12:43:57 -07:00
joaquintides 454fc7501e extended shutting down VS C4324 warning 2023-03-20 12:43:57 -07:00
joaquintides a89c62728a removed unnecessary mutable 2023-03-20 12:43:57 -07:00
joaquintides 1d8c0f91f8 avoided name hiding 2023-03-20 12:43:57 -07:00
joaquintides 25ba8e4dfc shut down VS C4324 warning 2023-03-20 12:43:57 -07:00
joaquintides fb4437219f fixed memory ordering in atomic_integral 2023-03-20 12:43:57 -07:00
joaquintides 0d1fdae84c implemented safe concurrent_table::load_factor 2023-03-20 12:43:57 -07:00
joaquintides c5debf11cf added explicit disabling/enabling of parallel algorithms 2023-03-20 12:43:57 -07:00
Christian Mazakas 9e4f89aa43 Continue fleshing out insert_tests 2023-03-20 12:43:57 -07:00
Christian Mazakas 12935eb695 Add minimal prototype of concurrent_flat_map 2023-03-20 12:43:57 -07:00
Christian Mazakas 2386188d69 Relocate macros needed by test suite into detail/fwd.hpp 2023-03-20 12:43:57 -07:00
Christian Mazakas e4b32f7f5e Update rw_spinlock impl to mirror github.com/pdimov's impl 2023-03-20 12:43:57 -07:00
Christian Mazakas 3df435d4d3 Temporarily suppress warning about memset'ing non-trivial types by using a constructor loop 2023-03-20 12:43:57 -07:00
Christian Mazakas ed076facc4 Stabilize ABI of cacheline_protected by hardcoding its alignment to 64 2023-03-20 12:43:57 -07:00
Christian Mazakas b09c5bdcf0 Add missing <execution> header 2023-03-20 12:43:57 -07:00
Christian Mazakas 6abb94bb68 Silence unused variable warnings in for_all_elements_exec 2023-03-20 12:43:57 -07:00
Christian Mazakas d2b1f095c8 Update atomic_uint nsdmi to eschew potentially deleted copy constructor 2023-03-20 12:43:57 -07:00
joaquintides 3d34571654 tried yest another variation for for_all_elements_while 2023-03-20 12:43:57 -07:00
joaquintides d683b3ac3e disabled use of match_really_occupied in for_all_elements_while 2023-03-20 12:43:57 -07:00
joaquintides e16a8737f4 shut down GCC Winterference-size 2023-03-20 12:43:57 -07:00
joaquintides 8f348aea26 temporarily disabled parallel algorithms 2023-03-20 12:43:57 -07:00
joaquintides 9a8ba2bdff re-enabled match_really_occupied in for_all_elements_while 2023-03-20 12:43:57 -07:00
joaquintides 3aaefdcc78 completed concurrent_table's API 2023-03-20 12:43:57 -07:00
joaquintides ca59ed8c50 avoided usage of match_really_occupied on for_all_elements_while (used by rehash) 2023-03-20 12:43:57 -07:00
joaquintides ff78ef25c0 removed unnecessary forwards and captures 2023-03-20 12:43:57 -07:00
joaquintides ec487b5c6c stylistic 2023-03-20 12:43:57 -07:00
joaquintides 0a03ed8170 weakened atomic_integral ops, fixed some trivial bugs 2023-03-20 12:43:57 -07:00
joaquintides 5881dcc2b2 done reverse unlock in multimutex 2023-03-20 12:43:57 -07:00
joaquintides 2070cbe2cc fixed regression in table_core(table_core&&) 2023-03-20 12:43:57 -07:00
joaquintides 8198b9c57c dropped erroneous call to user-provided f 2023-03-20 12:43:57 -07:00
joaquintides c35e9fc631 made slot setting on insertion exception safe 2023-03-20 12:43:57 -07:00
joaquintides f244ba55de switched from available to size_ 2023-03-20 12:43:57 -07:00
joaquintides 5e225fe46c micro-optimized reserve_available 2023-03-20 12:43:57 -07:00
joaquintides 06535e518e uploaded first draft of foa::concurrent_table 2023-03-20 12:43:57 -07:00
joaquintides 81584c22cf swapped member order in alloc_cted_insert_type to suppress padding warnings 2023-03-20 12:43:57 -07:00
joaquintides 83040211e8 recovered warninng supression pragmas 2023-03-20 12:43:56 -07:00
joaquintides 4ed301df97 refactored insert_type creation in emplace 2023-03-20 12:43:56 -07:00
joaquintides 8010f506a6 added SizeImpl and fixed some size arithmetic bugs 2023-03-20 12:43:56 -07:00
joaquintides 840ea1ce5c refactored some more for future concurrent_table 2023-03-20 12:43:56 -07:00
joaquintides 447306bfb9 stylistic 2023-03-20 12:43:56 -07:00
joaquintides 4569c1bec0 changed type of N to std::size_t 2023-03-20 12:43:56 -07:00
joaquintides 37ad547e18 more fixed sign issues in Neon ops 2023-03-20 12:43:56 -07:00
joaquintides e037ee8d82 fixed syntax error 2023-03-20 12:43:56 -07:00
joaquintides 9bbadfdb34 fixed sign issues in Neon ops 2023-03-20 12:43:56 -07:00
joaquintides c1b63f68c8 added missing op to plain_integral 2023-03-20 12:43:56 -07:00
joaquintides 0590e3bf8c typo 2023-03-20 12:43:56 -07:00
joaquintides 8f17f5f548 fixed static_assert 2023-03-20 12:43:56 -07:00
joaquintides 3a8bea9e1d parameterized group15 and core 2023-03-20 12:43:56 -07:00
joaquintides b33b354818 extended caae8eb9ac9a47dd224b81f22efe8ea34b084d54 to rest of "using typename"s 2023-03-20 12:43:56 -07:00
joaquintides b08837b93e added missing template keyword 2023-03-20 12:43:56 -07:00
joaquintides b3b840df30 extended Wshadow supression to foa::table 2023-03-20 12:43:56 -07:00
joaquintides 2ef47f0813 reformulated using to workaround VS problem 2023-03-20 12:43:56 -07:00
joaquintides 27f5513911 published max_load_factor 2023-03-20 12:43:56 -07:00
joaquintides a5100a9d35 fixed incomplete porting of PR187 2023-03-20 12:43:56 -07:00
joaquintides ccbc639677 made table_core protected interface public 2023-03-20 12:43:56 -07:00
joaquintides 18725a682d fixed previous fix 2023-03-20 12:43:56 -07:00
joaquintides dac1135199 fixed usage syntax for destroy_on_exit 2023-03-20 12:43:56 -07:00
joaquintides ead55a8938 s/noexcept(...)/noexcept(noexcept(...)) 2023-03-20 12:43:56 -07:00
joaquintides 8f39001ff0 fixed trivial syntax error 2023-03-20 12:43:56 -07:00
joaquintides 8b056b902e split foa::table in table and core in preparation for foa::concurrent_table 2023-03-20 12:43:56 -07:00
Christian Mazakas 5724adbbe9 Merge pull request #187 from boostorg/feature/foa_fast_iteration
Feature/foa fast iteration
2023-03-20 12:43:29 -07:00
joaquintides 1c5640cfbe s/BOOST_UNORDERED_ASSUME/BOOST_ASSERT 2023-03-08 19:12:46 +01:00
joaquintides 4d982e133e added is-not-end assumption on table_iterator::increment 2023-03-08 18:29:55 +01:00
115 changed files with 14070 additions and 1723 deletions
+149 -22
View File
@@ -6,11 +6,12 @@ local library = "unordered";
local triggers =
{
branch: [ "master", "develop", "feature/*", "bugfix/*", "fix/*", "pr/*" ]
branch: [ "master", "develop", "bugfix/*", "fix/*", "pr/*" ]
};
local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' };
local asan = { ASAN: '1' };
local tsan = { TSAN: '1' };
local linux_pipeline(name, image, environment, packages = "", sources = [], arch = "amd64") =
{
@@ -157,16 +158,29 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),
linux_pipeline(
"Linux 18.04 GCC 8 32/64",
"Linux 18.04 GCC 8 32/64 (03,11)",
"cppalliance/droneubuntu1804:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '03,11,14,17', ADDRMD: '32,64' },
{ TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '03,11', ADDRMD: '32,64' },
"g++-8-multilib",
),
linux_pipeline(
"Linux 20.04 GCC 9* 32/64",
"Linux 18.04 GCC 8 32/64 (14,17)",
"cppalliance/droneubuntu1804:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '14,17', ADDRMD: '32,64' },
"g++-8-multilib",
),
linux_pipeline(
"Linux 20.04 GCC 9* 32/64 (03,11,14)",
"cppalliance/droneubuntu2004:1",
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32,64' },
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14', ADDRMD: '32,64' },
),
linux_pipeline(
"Linux 20.04 GCC 9* 32/64 (17,2a)",
"cppalliance/droneubuntu2004:1",
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a', ADDRMD: '32,64' },
),
linux_pipeline(
@@ -177,36 +191,77 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),
linux_pipeline(
"Linux 20.04 GCC 9* S390x",
"Linux 20.04 GCC 9* S390x (03,11,14)",
"cppalliance/droneubuntu2004:multiarch",
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a' },
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14' },
arch="s390x",
),
linux_pipeline(
"Linux 20.04 GCC 10 32/64",
"Linux 20.04 GCC 9* S390x (17,2a)",
"cppalliance/droneubuntu2004:multiarch",
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a' },
arch="s390x",
),
linux_pipeline(
"Linux 20.04 GCC 10 32/64 (03,11,14)",
"cppalliance/droneubuntu2004:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '03,11,14,17,20', ADDRMD: '32,64' },
{ TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '03,11,14', ADDRMD: '32,64' },
"g++-10-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 11* 32/64",
"Linux 20.04 GCC 10 32/64 (17,20)",
"cppalliance/droneubuntu2004:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '17,20', ADDRMD: '32,64' },
"g++-10-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 11* 32/64 (03,11,14)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32,64' },
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14', ADDRMD: '32,64' },
),
linux_pipeline(
"Linux 22.04 GCC 11* 32/64 (17,2a)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a', ADDRMD: '32,64' },
),
linux_pipeline(
"Linux 22.04 GCC 12 32 ASAN (03,11,14)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11,14', ADDRMD: '32' } + asan,
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11', ADDRMD: '32' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 32 ASAN (17,20,2b)",
"Linux 22.04 GCC 12 32 ASAN (14)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20,2b', ADDRMD: '32' } + asan,
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '14', ADDRMD: '32' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 32 ASAN (17)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '32' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 32 ASAN (20)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '32' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 32 ASAN (2b)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '32' } + asan,
"g++-12-multilib",
),
@@ -218,12 +273,47 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),
linux_pipeline(
"Linux 22.04 GCC 12 64 ASAN (17,20,2b)",
"Linux 22.04 GCC 12 64 ASAN (17)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20,2b', ADDRMD: '64' } + asan,
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '64' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 64 ASAN (20)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '64' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 64 ASAN (2b)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '64' } + asan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 22.04 GCC 12 64 TSAN (11,14,17,20,2b)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '11,14,17,20,2b', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests' } + tsan,
"g++-12-multilib",
),
linux_pipeline(
"Linux 23.04 GCC 13 32/64 (03,11,14)",
"cppalliance/droneubuntu2304:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-13', CXXSTD: '03,11,14', ADDRMD: '32,64' },
"g++-13 g++-13-multilib",
),
linux_pipeline(
"Linux 23.04 GCC 13 32/64 (17,20,2b)",
"cppalliance/droneubuntu2304:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-13', CXXSTD: '17,20,2b', ADDRMD: '32,64' },
"g++-13 g++-13-multilib",
),
linux_pipeline(
"Linux 16.04 Clang 3.5",
"cppalliance/droneubuntu1604:1",
@@ -330,19 +420,40 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),
linux_pipeline(
"Linux 22.04 Clang 14 UBSAN",
"Linux 22.04 Clang 14 UBSAN (03,11,14)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20' } + ubsan,
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14' } + ubsan,
"clang-14",
),
linux_pipeline(
"Linux 22.04 Clang 14 ASAN",
"Linux 22.04 Clang 14 UBSAN (17,20)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20' } + asan,
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '17,20' } + ubsan,
"clang-14",
),
linux_pipeline(
"Linux 22.04 Clang 14 ASAN (03,11,14)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14' } + asan,
"clang-14",
),
linux_pipeline(
"Linux 22.04 Clang 14 ASAN (17,20)",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '17,20' } + asan,
"clang-14",
),
linux_pipeline(
"Linux 22.04 Clang 14 libc++ 64 TSAN",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', ADDRMD: '64', TARGET: 'libs/unordered/test//cfoa_tests', CXXSTD: '11,14,17,20', STDLIB: 'libc++' } + tsan,
"clang-14 libc++-14-dev libc++abi-14-dev",
),
linux_pipeline(
"Linux 22.04 Clang 15",
"cppalliance/droneubuntu2204:1",
@@ -352,8 +463,18 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),
macos_pipeline(
"MacOS 10.15 Xcode 12.2 UBSAN",
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11,14,1z' } + ubsan,
"MacOS 10.15 Xcode 12.2 UBSAN (03,11)",
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11' } + ubsan,
),
macos_pipeline(
"MacOS 10.15 Xcode 12.2 UBSAN (14)",
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '14' } + ubsan,
),
macos_pipeline(
"MacOS 10.15 Xcode 12.2 UBSAN (1z)",
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '1z' } + ubsan,
),
macos_pipeline(
@@ -362,6 +483,12 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
xcode_version = "13.4.1", osx_version = "monterey", arch = "arm64",
),
macos_pipeline(
"MacOS 12.4 Xcode 13.4.1 TSAN",
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '11,14,1z', TARGET: 'libs/unordered/test//cfoa_tests' } + tsan,
xcode_version = "13.4.1", osx_version = "monterey", arch = "arm64",
),
windows_pipeline(
"Windows VS2015 msvc-14.0",
"cppalliance/dronevs2015",
+3 -1
View File
@@ -7,6 +7,8 @@
set -ex
export PATH=~/.local/bin:/usr/local/bin:$PATH
: ${TARGET:="libs/$LIBRARY/test"}
DRONE_BUILD_DIR=$(pwd)
BOOST_BRANCH=develop
@@ -22,4 +24,4 @@ python tools/boostdep/depinst/depinst.py $LIBRARY
./b2 -d0 headers
echo "using $TOOLSET : : $COMPILER ;" > ~/user-config.jam
./b2 -j3 libs/$LIBRARY/test toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS}
./b2 -j3 $TARGET toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${STDLIB:+stdlib=$STDLIB} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${TSAN:+thread-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS}
+13 -3
View File
@@ -15,7 +15,6 @@ on:
- master
- develop
- bugfix/**
- feature/**
- fix/**
- pr/**
@@ -52,7 +51,10 @@ jobs:
- { name: "gcc-12 w/ sanitizers (17,20,2b)", sanitize: yes,
compiler: gcc-12, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
- { name: Collect coverage, coverage: yes,
compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64', ccache_key: "cov" }
compiler: gcc-12, cxxstd: '03,20', os: ubuntu-22.04, install: 'g++-12-multilib', address-model: '32,64', ccache_key: "cov" }
- { name: "cfoa tsan (gcc)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: gcc-12,
targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes }
# Linux, clang, libc++
- { compiler: clang-7, cxxstd: '03,11,14,17', os: ubuntu-20.04, stdlib: libc++, install: 'clang-7 libc++-7-dev libc++abi-7-dev' }
@@ -65,15 +67,21 @@ jobs:
compiler: clang-12, cxxstd: '17,20,2b', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev', ccache_key: "san2" }
- { compiler: clang-13, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-13 libc++-13-dev libc++abi-13-dev' }
- { compiler: clang-14, cxxstd: '03,11,14,17,20,2b', os: ubuntu-22.04, stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' }
# not using libc++ because of https://github.com/llvm/llvm-project/issues/52771
- { name: "clang-14 w/ sanitizers (03,11,14)", sanitize: yes,
compiler: clang-14, cxxstd: '03,11,14', os: ubuntu-22.04, ccache_key: "san1" }
- { name: "clang-14 w/ sanitizers (17,20,2b)", sanitize: yes,
compiler: clang-14, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
- { name: "cfoa tsan (clang)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: clang-14,
targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes,
stdlib: libc++, install: 'clang-14 libc++-14-dev libc++abi-14-dev' }
# OSX, clang
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, }
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes }
- { compiler: clang, cxxstd: '11,14,17,2a', os: macos-12, thread-sanitize: yes, targets: 'libs/unordered/test//cfoa_tests' }
timeout-minutes: 180
runs-on: ${{matrix.os}}
@@ -184,6 +192,8 @@ jobs:
B2_COMPILER: ${{matrix.compiler}}
B2_CXXSTD: ${{matrix.cxxstd}}
B2_SANITIZE: ${{matrix.sanitize}}
B2_TSAN: ${{matrix.thread-sanitize}}
B2_TARGETS: ${{matrix.targets}}
B2_STDLIB: ${{matrix.stdlib}}
# More entries can be added in the same way, see the B2_ARGS assignment in ci/enforce.sh for the possible keys.
# B2_DEFINES: ${{matrix.defines}}
@@ -197,7 +207,7 @@ jobs:
- name: Run tests
if: '!matrix.coverity'
run: ci/build.sh
run: B2_TARGETS=${{matrix.targets}} ci/build.sh
- name: Upload coverage
if: matrix.coverage
+1
View File
@@ -22,6 +22,7 @@ target_link_libraries(boost_unordered
Boost::mp11
Boost::predef
Boost::preprocessor
Boost::static_assert
Boost::throw_exception
Boost::tuple
Boost::type_traits
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

+3 -1
View File
@@ -13,8 +13,10 @@
include::unordered/intro.adoc[]
include::unordered/buckets.adoc[]
include::unordered/hash_equality.adoc[]
include::unordered/comparison.adoc[]
include::unordered/regular.adoc[]
include::unordered/concurrent.adoc[]
include::unordered/compliance.adoc[]
include::unordered/structures.adoc[]
include::unordered/benchmarks.adoc[]
include::unordered/rationale.adoc[]
include::unordered/ref.adoc[]
+260
View File
@@ -431,3 +431,263 @@ h|unsuccessful lookup
|===
== boost::concurrent_flat_map
All benchmarks were created using:
* `https://spec.oneapi.io/versions/latest/elements/oneTBB/source/containers/concurrent_hash_map_cls.html[oneapi::tbb::concurrent_hash_map^]<int, int>`
* `https://github.com/greg7mdp/gtl/blob/main/docs/phmap.md[gtl::parallel_flat_hash_map^]<int, int>` with 64 submaps
* `boost::concurrent_flat_map<int, int>`
The source code can be https://github.com/boostorg/boost_unordered_benchmarks/tree/boost_concurrent_flat_map[found here^].
The benchmarks exercise a number of threads _T_ (between 1 and 16) concurrently performing operations
randomly chosen among **update**, **successful lookup** and **unsuccessful lookup**. The keys used in the
operations follow a https://en.wikipedia.org/wiki/Zipf%27s_law#Formal_definition[Zipf distribution^]
with different _skew_ parameters: the higher the skew, the more concentrated are the keys in the lower values
of the covered range.
=== GCC 12, x64
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
=== Clang 15, x64
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
=== Visual Studio 2022, x64
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
=== Clang 12, ARM64
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
=== GCC 12, x86
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
=== Clang 15, x86
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
=== Visual Studio 2022, x86
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.01.png]
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.5.png]
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.99.png]
h|500k updates, 4.5M lookups +
skew=0.01
h|500k updates, 4.5M lookups +
skew=0.5
h|500k updates, 4.5M lookups +
skew=0.99
|===
[caption=]
[cols="3*^.^a", frame=all, grid=all]
|===
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.01.png]
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.5.png]
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.99.png]
h|5M updates, 45M lookups +
skew=0.01
h|5M updates, 45M lookups +
skew=0.5
h|5M updates, 45M lookups +
skew=0.99
|===
+9 -174
View File
@@ -2,9 +2,9 @@
:idprefix: buckets_
:imagesdir: ../diagrams
= The Data Structure
= Basics of Hash Tables
The containers are made up of a number of 'buckets', each of which can contain
The containers are made up of a number of _buckets_, each of which can contain
any number of elements. For example, the following diagram shows a <<unordered_set,`boost::unordered_set`>> with 7 buckets containing 5 elements, `A`,
`B`, `C`, `D` and `E` (this is just for illustration, containers will typically
have more buckets).
@@ -12,8 +12,7 @@ have more buckets).
image::buckets.png[]
In order to decide which bucket to place an element in, the container applies
the hash function, `Hash`, to the element's key (for `unordered_set` and
`unordered_multiset` the key is the whole element, but is referred to as the key
the hash function, `Hash`, to the element's key (for sets the key is the whole element, but is referred to as the key
so that the same terminology can be used for sets and maps). This returns a
value of type `std::size_t`. `std::size_t` has a much greater range of values
then the number of buckets, so the container applies another transformation to
@@ -53,8 +52,7 @@ h|*Method* h|*Description*
|`size_type bucket_count() const`
|The number of buckets.
2+^h| *Closed-addressing containers only* +
`boost::unordered_[multi]set`, `boost::unordered_[multi]map`
2+^h| *Closed-addressing containers only*
h|*Method* h|*Description*
|`size_type max_bucket_count() const`
@@ -80,7 +78,7 @@ h|*Method* h|*Description*
|===
== Controlling the number of buckets
== Controlling the Number of Buckets
As more elements are added to an unordered associative container, the number
of collisions will increase causing performance to degrade.
@@ -90,8 +88,8 @@ calling `rehash`.
The standard leaves a lot of freedom to the implementer to decide how the
number of buckets is chosen, but it does make some requirements based on the
container's 'load factor', the number of elements divided by the number of buckets.
Containers also have a 'maximum load factor' which they should try to keep the
container's _load factor_, the number of elements divided by the number of buckets.
Containers also have a _maximum load factor_ which they should try to keep the
load factor below.
You can't control the bucket count directly but there are two ways to
@@ -133,9 +131,7 @@ h|*Method* h|*Description*
|`void rehash(size_type n)`
|Changes the number of buckets so that there at least `n` buckets, and so that the load factor is less than the maximum load factor.
2+^h| *Open-addressing containers only* +
`boost::unordered_flat_set`, `boost::unordered_flat_map` +
`boost::unordered_node_set`, `boost::unordered_node_map` +
2+^h| *Open-addressing and concurrent containers only*
h|*Method* h|*Description*
|`size_type max_load() const`
@@ -143,7 +139,7 @@ h|*Method* h|*Description*
|===
A note on `max_load` for open-addressing containers: the maximum load will be
A note on `max_load` for open-addressing and concurrent containers: the maximum load will be
(`max_load_factor() * bucket_count()`) right after `rehash` or on container creation, but may
slightly decrease when erasing elements in high-load situations. For instance, if we
have a <<unordered_flat_map,`boost::unordered_flat_map`>> with `size()` almost
@@ -151,165 +147,4 @@ at `max_load()` level and then erase 1,000 elements, `max_load()` may decrease b
few dozen elements. This is done internally by Boost.Unordered in order
to keep its performance stable, and must be taken into account when planning for rehash-free insertions.
== Iterator Invalidation
It is not specified how member functions other than `rehash` and `reserve` affect
the bucket count, although `insert` can only invalidate iterators
when the insertion causes the container's load to be greater than the maximum allowed.
For most implementations this means that `insert` will only
change the number of buckets when this happens. Iterators can be
invalidated by calls to `insert`, `rehash` and `reserve`.
As for pointers and references,
they are never invalidated for node-based containers
(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`),
but they will when rehashing occurs for
`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because
these containers store elements directly into their holding buckets, so
when allocating a new bucket array the elements must be transferred by means of move construction.
In a similar manner to using `reserve` for ``vector``s, it can be a good idea
to call `reserve` before inserting a large number of elements. This will get
the expensive rehashing out of the way and let you store iterators, safe in
the knowledge that they won't be invalidated. If you are inserting `n`
elements into container `x`, you could first call:
```
x.reserve(n);
```
Note:: `reserve(n)` reserves space for at least `n` elements, allocating enough buckets
so as to not exceed the maximum load factor.
+
Because the maximum load factor is defined as the number of elements divided by the total
number of available buckets, this function is logically equivalent to:
+
```
x.rehash(std::ceil(n / x.max_load_factor()))
```
+
See the <<unordered_map_rehash,reference for more details>> on the `rehash` function.
== Fast Closed Addressing Implementation
++++
<style>
.imageblock > .title {
text-align: inherit;
}
</style>
++++
Boost.Unordered sports one of the fastest implementations of closed addressing, also commonly known as https://en.wikipedia.org/wiki/Hash_table#Separate_chaining[separate chaining]. An example figure representing the data structure is below:
[#img-bucket-groups,.text-center]
.A simple bucket group approach
image::bucket-groups.png[align=center]
An array of "buckets" is allocated and each bucket in turn points to its own individual linked list. This makes meeting the standard requirements of bucket iteration straight-forward. Unfortunately, iteration of the entire container is often times slow using this layout as each bucket must be examined for occupancy, yielding a time complexity of `O(bucket_count() + size())` when the standard requires complexity to be `O(size())`.
Canonical standard implementations will wind up looking like the diagram below:
[.text-center]
.The canonical standard approach
image::singly-linked.png[align=center,link=../diagrams/singly-linked.png,window=_blank]
It's worth noting that this approach is only used by pass:[libc++] and pass:[libstdc++]; the MSVC Dinkumware implementation uses a different one. A more detailed analysis of the standard containers can be found http://bannalia.blogspot.com/2013/10/implementation-of-c-unordered.html[here].
This unusually laid out data structure is chosen to make iteration of the entire container efficient by inter-connecting all of the nodes into a singly-linked list. One might also notice that buckets point to the node _before_ the start of the bucket's elements. This is done so that removing elements from the list can be done efficiently without introducing the need for a doubly-linked list. Unfortunately, this data structure introduces a guaranteed extra indirection. For example, to access the first element of a bucket, something like this must be done:
```c++
auto const idx = get_bucket_idx(hash_function(key));
node* p = buckets[idx]; // first load
node* n = p->next; // second load
if (n && is_in_bucket(n, idx)) {
value_type const& v = *n; // third load
// ...
}
```
With a simple bucket group layout, this is all that must be done:
```c++
auto const idx = get_bucket_idx(hash_function(key));
node* n = buckets[idx]; // first load
if (n) {
value_type const& v = *n; // second load
// ...
}
```
In practice, the extra indirection can have a dramatic performance impact to common operations such as `insert`, `find` and `erase`. But to keep iteration of the container fast, Boost.Unordered introduces a novel data structure, a "bucket group". A bucket group is a fixed-width view of a subsection of the buckets array. It contains a bitmask (a `std::size_t`) which it uses to track occupancy of buckets and contains two pointers so that it can form a doubly-linked list with non-empty groups. An example diagram is below:
[#img-fca-layout]
.The new layout used by Boost
image::fca.png[align=center]
Thus container-wide iteration is turned into traversing the non-empty bucket groups (an operation with constant time complexity) which reduces the time complexity back to `O(size())`. In total, a bucket group is only 4 words in size and it views `sizeof(std::size_t) * CHAR_BIT` buckets meaning that for all common implementations, there's only 4 bits of space overhead per bucket introduced by the bucket groups.
A more detailed description of Boost.Unordered's closed-addressing implementation is
given in an
https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article].
For more information on implementation rationale, read the
xref:#rationale_boostunordered_multiset_and_boostunordered_multimap[corresponding section].
== Open Addressing Implementation
The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and
`boost:unordered_flat_set`/`unordered_node_set`.
[#img-foa-layout]
.Open-addressing layout used by Boost.Unordered.
image::foa.png[align=center]
As with all open-addressing containers, elements (or pointers to the element nodes in the case of
`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array.
This array is logically divided into 2^_n_^ _groups_ of 15 elements each.
In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^
16-byte words.
[#img-foa-metadata]
.Breakdown of a metadata word.
image::foa-metadata.png[align=center]
A metadata word is divided into 15 _h_~_i_~ bytes (one for each associated
bucket), and an _overflow byte_ (_ofw_ in the diagram). The value of _h_~_i_~ is:
- 0 if the corresponding bucket is empty.
- 1 to encode a special empty bucket called a _sentinel_, which is used internally to
stop iteration when the container has been fully traversed.
- If the bucket is occupied, a _reduced hash value_ obtained from the hash value of
the element.
When looking for an element with hash value _h_, SIMD technologies such as
https://en.wikipedia.org/wiki/SSE2[SSE2] and
https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)[Neon] allow us
to very quickly inspect the full metadata word and look for the reduced value of _h_ among all the
15 buckets with just a handful of CPU instructions: non-matching buckets can be
readily discarded, and those whose reduced hash value matches need be inspected via full
comparison with the corresponding element. If the looked-for element is not present,
the overflow byte is inspected:
- If the bit in the position _h_ mod 8 is zero, lookup terminates (and the
element is not present).
- If the bit is set to 1 (the group has been _overflowed_), further groups are
checked using https://en.wikipedia.org/wiki/Quadratic_probing[_quadratic probing_], and
the process is repeated.
Insertion is algorithmically similar: empty buckets are located using SIMD,
and when going past a full group its corresponding overflow bit is set to 1.
In architectures without SIMD support, the logical layout stays the same, but the metadata
word is codified using a technique we call _bit interleaving_: this layout allows us
to emulate SIMD with reasonably good performance using only standard arithmetic and
logical operations.
[#img-foa-metadata-interleaving]
.Bit-interleaved metadata word.
image::foa-metadata-interleaving.png[align=center]
A more detailed description of Boost.Unordered's open-addressing implementation is
given in an
https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article].
For more information on implementation rationale, read the
xref:#rationale_boostunordered_flat_set_and_boostunordered_flat_map[corresponding section].
+2 -1
View File
@@ -6,8 +6,9 @@
:github-pr-url: https://github.com/boostorg/unordered/pull
:cpp: C++
== Release 1.83.0
== Release 1.83.0 - Major update
* Added `boost::concurrent_flat_map`, a fast, thread-safe hashmap based on open addressing.
* Sped up iteration of open-addressing containers.
== Release 1.82.0 - Major update
+62 -5
View File
@@ -5,7 +5,7 @@
:cpp: C++
== Closed-addressing containers
== Closed-addressing Containers
`unordered_[multi]set` and `unordered_[multi]map` are intended to provide a conformant
implementation of the {cpp}20 standard that will work with {cpp}98 upwards.
@@ -13,7 +13,7 @@ This wide compatibility does mean some compromises have to be made.
With a compiler and library that fully support {cpp}11, the differences should
be minor.
=== Move emulation
=== Move Emulation
Support for move semantics is implemented using Boost.Move. If rvalue
references are available it will use them, but if not it uses a close,
@@ -25,7 +25,7 @@ but imperfect emulation. On such compilers:
* The containers themselves are not movable.
* Argument forwarding is not perfect.
=== Use of allocators
=== Use of Allocators
{cpp}11 introduced a new allocator system. It's backwards compatible due to
the lax requirements for allocators in the old standard, but might need
@@ -58,7 +58,7 @@ Due to imperfect move emulation, some assignments might check
`propagate_on_container_copy_assignment` on some compilers and
`propagate_on_container_move_assignment` on others.
=== Construction/Destruction using allocators
=== Construction/Destruction Using Allocators
The following support is required for full use of {cpp}11 style
construction/destruction:
@@ -117,7 +117,7 @@ Variadic constructor arguments for `emplace` are only used when both
rvalue references and variadic template parameters are available.
Otherwise `emplace` can only take up to 10 constructors arguments.
== Open-addressing containers
== Open-addressing Containers
The C++ standard does not currently provide any open-addressing container
specification to adhere to, so `boost::unordered_flat_set`/`unordered_node_set` and
@@ -144,4 +144,61 @@ The main differences with C++ unordered associative containers are:
** Pointer stability is not kept under rehashing.
** There is no API for node extraction/insertion.
== Concurrent Containers
There is currently no specification in the C++ standard for this or any other concurrent
data structure. `boost::concurrent_flat_map` takes the same template parameters as `std::unordered_map`
and all the maps provided by Boost.Unordered, and its API is modelled after that of
`boost::unordered_flat_map` with the crucial difference that iterators are not provided
due to their inherent problems in concurrent scenarios (high contention, prone to deadlocking):
so, `boost::concurrent_flat_map` is technically not a
https://en.cppreference.com/w/cpp/named_req/Container[Container^], although
it meets all the requirements of https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^]
containers except those implying iterators.
In a non-concurrent unordered container, iterators serve two main purposes:
* Access to an element previously located via lookup.
* Container traversal.
In place of iterators, `boost::concurrent_flat_map` uses _internal visitation_
facilities as a thread-safe substitute. Classical operations returning an iterator to an
element already existing in the container, like for instance:
[source,c++]
----
iterator find(const key_type& k);
std::pair<iterator, bool> insert(const value_type& obj);
----
are transformed to accept a _visitation function_ that is passed such element:
[source,c++]
----
template<class F> size_t visit(const key_type& k, F f);
template<class F> bool insert_or_visit(const value_type& obj, F f);
----
(In the second case `f` is only invoked if there's an equivalent element
to `obj` in the table, not if insertion is successful). Container traversal
is served by:
[source,c++]
----
template<class F> size_t visit_all(F f);
----
of which there are parallelized versions in C++17 compilers with parallel
algorithm support. In general, the interface of `boost::concurrent_flat_map`
is derived from that of `boost::unordered_flat_map` by a fairly straightforward
process of replacing iterators with visitation where applicable. If
`iterator` and `const_iterator` provide mutable and const access to elements,
respectively, here visitation is granted mutable or const access depending on
the constness of the member function used (there are also `*cvisit` overloads for
explicit const visitation).
The one notable operation not provided is `operator[]`/`at`, which can be
replaced, if in a more convoluted manner, by
xref:#concurrent_flat_map_try_emplace_or_cvisit[`try_emplace_or_visit`].
//-
+182
View File
@@ -0,0 +1,182 @@
[#concurrent]
= Concurrent Containers
:idprefix: concurrent_
Boost.Unordered currently provides just one concurrent container named `boost::concurrent_flat_map`.
`boost::concurrent_flat_map` is a hash table that allows concurrent write/read access from
different threads without having to implement any synchronzation mechanism on the user's side.
[source,c++]
----
std::vector<int> input;
boost::concurrent_flat_map<int,int> m;
...
// process input in parallel
const int num_threads = 8;
std::vector<std::jthread> threads;
std::size_t chunk = input.size() / num_threads; // how many elements per thread
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&,i] {
// calculate the portion of input this thread takes care of
std::size_t start = i * chunk;
std::size_t end = (i == num_threads - 1)? input.size(): (i + 1) * chunk;
for (std::size_t n = start; n < end; ++n) {
m.emplace(input[n], calculation(input[n]));
}
});
}
----
In the example above, threads access `m` without synchronization, just as we'd do in a
single-threaded scenario. In an ideal setting, if a given workload is distributed among
_N_ threads, execution is _N_ times faster than with one thread —this limit is
never attained in practice due to synchronization overheads and _contention_ (one thread
waiting for another to leave a locked portion of the map), but `boost::concurrent_flat_map`
is designed to perform with very little overhead and typically achieves _linear scaling_
(that is, performance is proportional to the number of threads up to the number of
logical cores in the CPU).
== Visitation-based API
The first thing a new user of `boost::concurrent_flat_map` will notice is that this
class _does not provide iterators_ (which makes it technically
not a https://en.cppreference.com/w/cpp/named_req/Container[Container^]
in the C++ standard sense). The reason for this is that iterators are inherently
thread-unsafe. Consider this hypothetical code:
[source,c++]
----
auto it = m.find(k); // A: get an iterator pointing to the element with key k
if (it != m.end() ) {
some_function(*it); // B: use the value of the element
}
----
In a multithreaded scenario, the iterator `it` may be invalid at point B if some other
thread issues an `m.erase(k)` operation between A and B. There are designs that
can remedy this by making iterators lock the element they point to, but this
approach lends itself to high contention and can easily produce deadlocks in a program.
`operator[]` has similar concurrency issues, and is not provided by
`boost::concurrent_flat_map` either. Instead, element access is done through
so-called _visitation functions_:
[source,c++]
----
m.visit(k, [](const auto& x) { // x is the element with key k (if it exists)
some_function(x); // use it
});
----
The visitation function passed by the user (in this case, a lambda function)
is executed internally by `boost::concurrent_flat_map` in
a thread-safe manner, so it can access the element without worrying about other
threads interfering in the process.
On the other hand, a visitation function can _not_ access the container itself:
[source,c++]
----
m.visit(k, [&](const auto& x) {
some_function(x, m.size()); // forbidden: m can't be accessed inside visitation
});
----
Access to a different container is allowed, though:
[source,c++]
----
m.visit(k, [&](const auto& x) {
if (some_function(x)) {
m2.insert(x); // OK, m2 is a different boost::concurrent_flat_map
}
});
----
But, in general, visitation functions should be as lightweight as possible to
reduce contention and increase parallelization. In some cases, moving heavy work
outside of visitation may be beneficial:
[source,c++]
----
std::optional<value_type> o;
bool found = m.visit(k, [&](const auto& x) {
o = x;
});
if (found) {
some_heavy_duty_function(*o);
}
----
Visitation is prominent in the API provided by `boost::concurrent_flat_map`, and
many classical operations have visitation-enabled variations:
[source,c++]
----
m.insert_or_visit(x, [](auto& y) {
// if insertion failed because of an equivalent element y,
// do something with it, for instance:
++y.second; // increment the mapped part of the element
});
----
Note that in this last example the visitation function could actually _modify_
the element: as a general rule, operations on a `boost::concurrent_flat_map` `m`
will grant visitation functions const/non-const access to the element depending on whether
`m` is const/non-const. Const access can be always be explicitly requested
by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result
in higher parallelization. Consult the xref:#concurrent_flat_map[reference]
for a complete list of available operations.
== Whole-Table Visitation
In the absence of iterators, `boost::concurrent_flat_map` provides `visit_all`
as an alternative way to process all the elements in the map:
[source,c++]
----
m.visit_all([](auto& x) {
x.second = 0; // reset the mapped part of the element
});
----
In C++17 compilers implementing standard parallel algorithms, whole-table
visitation can be parallelized:
[source,c++]
----
m.visit_all(std::execution::par, [](auto& x) { // run in parallel
x.second = 0; // reset the mapped part of the element
});
----
There is another whole-table visitation operation, `erase_if`:
[source,c++]
----
m.erase_if([](auto& x) {
return x.second == 0; // erase the elements whose mapped value is zero
});
----
`erase_if` can also be parallelized. Note that, in order to increase efficiency,
these operations do not block the table during execution: this implies that elements
may be inserted, modified or erased by other threads during visitation. It is
advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map`
at any point in your program.
== Blocking Operations
``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any
Boost.Unordered container. Unlike most other operations, these are _blocking_,
that is, all other threads are prevented from accesing the tables involved while a copy, assignment,
clear or merge operation is in progress. Blocking is taken care of automatically by the library
and the user need not take any special precaution, but overall performance may be affected.
Another blocking operation is _rehashing_, which happens explicitly via `rehash`/`reserve`
or during insertion when the table's load hits `max_load()`. As with non-concurrent containers,
reserving space in advance of bulk insertions will generally speed up the process.
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -20,14 +20,14 @@ class unordered_map;
The hash function comes first as you might want to change the hash function
but not the equality predicate. For example, if you wanted to use the
http://www.isthe.com/chongo/tech/comp/fnv/[FNV-1 hash^] you could write:
https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash[FNV-1a hash^] you could write:
```
boost::unordered_map<std::string, int, hash::fnv_1>
boost::unordered_map<std::string, int, hash::fnv_1a>
dictionary;
```
There is an link:../../examples/fnv1.hpp[implementation of FNV-1^] in the examples directory.
There is an link:../../examples/fnv1.hpp[implementation of FNV-1a^] in the examples directory.
If you wish to use a different equality function, you will also need to use a matching hash function. For example, to implement a case insensitive dictionary you need to define a case insensitive equality predicate and hash function:
+61 -182
View File
@@ -4,26 +4,65 @@
:idprefix: intro_
:cpp: C++
For accessing data based on key lookup, the {cpp} standard library offers `std::set`,
`std::map`, `std::multiset` and `std::multimap`. These are generally
implemented using balanced binary trees so that lookup time has
logarithmic complexity. That is generally okay, but in many cases a
link:https://en.wikipedia.org/wiki/Hash_table[hash table^] can perform better, as accessing data has constant complexity,
on average. The worst case complexity is linear, but that occurs rarely and
with some care, can be avoided.
link:https://en.wikipedia.org/wiki/Hash_table[Hash tables^] are extremely popular
computer data structures and can be found under one form or another in virtually any programming
language. Whereas other associative structures such as rb-trees (used in {cpp} by `std::set` and `std::map`)
have logarithmic-time complexity for insertion and lookup, hash tables, if configured properly,
perform these operations in constant time on average, and are generally much faster.
Also, the existing containers require a 'less than' comparison object
to order their elements. For some data types this is impossible to implement
or isn't practical. In contrast, a hash table only needs an equality function
and a hash function for the key.
{cpp} introduced __unordered associative containers__ `std::unordered_set`, `std::unordered_map`,
`std::unordered_multiset` and `std::unordered_multimap` in {cpp}11, but research on hash tables
hasn't stopped since: advances in CPU architectures such as
more powerful caches, link:https://en.wikipedia.org/wiki/Single_instruction,_multiple_data[SIMD] operations
and increasingly available link:https://en.wikipedia.org/wiki/Multi-core_processor[multicore processors]
open up possibilities for improved hash-based data structures and new use cases that
are simply beyond reach of unordered associative containers as specified in 2011.
With this in mind, unordered associative containers were added to the {cpp}
standard. Boost.Unordered provides an implementation of the containers described in {cpp}11,
with some <<compliance,deviations from the standard>> in
order to work with non-{cpp}11 compilers and libraries.
Boost.Unordered offers a catalog of hash containers with different standards compliance levels,
performances and intented usage scenarios:
[caption=, title='Table {counter:table-counter}. Boost.Unordered containers']
[cols="1,1,.^1", frame=all, grid=all]
|===
^h|
^h|*Node-based*
^h|*Flat*
^.^h|*Closed addressing*
^m|
boost::unordered_set +
boost::unordered_map +
boost::unordered_multiset +
boost::unordered_multimap
^|
^.^h|*Open addressing*
^m| boost::unordered_node_set +
boost::unordered_node_map
^m| boost::unordered_flat_set +
boost::unordered_flat_map
^.^h|*Concurrent*
^|
^| `boost::concurrent_flat_map`
|===
* **Closed-addressing containers** are fully compliant with the C++ specification
for unordered associative containers and feature one of the fastest implementations
in the market within the technical constraints imposed by the required standard interface.
* **Open-addressing containers** rely on much faster data structures and algorithms
(more than 2 times faster in typical scenarios) while slightly diverging from the standard
interface to accommodate the implementation.
There are two variants: **flat** (the fastest) and **node-based**, which
provide pointer stability under rehashing at the expense of being slower.
* Finally, `boost::concurrent_flat_map` (the only **concurrent container** provided
at present) is a hashmap designed and implemented to be used in high-performance
multithreaded scenarios. Its interface is radically different from that of regular C++ containers.
All sets and maps in Boost.Unordered are instantiatied similarly as
`std::unordered_set` and `std::unordered_map`, respectively:
`unordered_set` and `unordered_multiset` are defined in the header
`<boost/unordered/unordered_set.hpp>`
[source,c++]
----
namespace boost {
@@ -32,178 +71,21 @@ namespace boost {
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<Key> >
class unordered_set;
class unordered_set;
// same for unordered_multiset, unordered_flat_set, unordered_node_set
template<
class Key,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<Key> >
class unordered_multiset;
}
----
`unordered_map` and `unordered_multimap` are defined in the header
`<boost/unordered/unordered_map.hpp>`
[source,c++]
----
namespace boost {
template <
class Key, class Mapped,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
class unordered_map;
template<
class Key, class Mapped,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
class unordered_multimap;
// same for unordered_multimap, unordered_flat_map, unordered_node_map
// and concurrent_flat_map
}
----
These containers, and all other implementations of standard unordered associative
containers, use an approach to its internal data structure design called
*closed addressing*. Starting in Boost 1.81, Boost.Unordered also provides containers
`boost::unordered_flat_set` and `boost::unordered_flat_map`, which use a
different data structure strategy commonly known as *open addressing* and depart in
a small number of ways from the standard so as to offer much better performance
in exchange (more than 2 times faster in typical scenarios):
[source,c++]
----
// #include <boost/unordered/unordered_flat_set.hpp>
//
// Note: no multiset version
namespace boost {
template <
class Key,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<Key> >
class unordered_flat_set;
}
----
[source,c++]
----
// #include <boost/unordered/unordered_flat_map.hpp>
//
// Note: no multimap version
namespace boost {
template <
class Key, class Mapped,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
class unordered_flat_map;
}
----
Starting in Boost 1.82, the containers `boost::unordered_node_set` and `boost::unordered_node_map`
are introduced: they use open addressing like `boost::unordered_flat_set` and `boost::unordered_flat_map`,
but internally store element _nodes_, like `boost::unordered_set` and `boost::unordered_map`,
which provide stability of pointers and references to the elements:
[source,c++]
----
// #include <boost/unordered/unordered_node_set.hpp>
//
// Note: no multiset version
namespace boost {
template <
class Key,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<Key> >
class unordered_node_set;
}
----
[source,c++]
----
// #include <boost/unordered/unordered_node_map.hpp>
//
// Note: no multimap version
namespace boost {
template <
class Key, class Mapped,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
class unordered_node_map;
}
----
These are all the containers provided by Boost.Unordered:
[caption=, title='Table {counter:table-counter}. Boost.Unordered containers']
[cols="1,1,.^1", frame=all, grid=rows]
|===
^h|
^h|*Node-based*
^h|*Flat*
^.^h|*Closed addressing*
^| `boost::unordered_set` +
`boost::unordered_map` +
`boost::unordered_multiset` +
`boost::unordered_multimap`
^|
^.^h|*Open addressing*
^| `boost::unordered_node_set` +
`boost::unordered_node_map`
^| `boost::unordered_flat_set` +
`boost::unordered_flat_map`
|===
Closed-addressing containers are pass:[C++]98-compatible. Open-addressing containers require a
reasonably compliant pass:[C++]11 compiler.
Boost.Unordered containers are used in a similar manner to the normal associative
containers:
[source,cpp]
----
typedef boost::unordered_map<std::string, int> map;
map x;
x["one"] = 1;
x["two"] = 2;
x["three"] = 3;
assert(x.at("one") == 1);
assert(x.find("missing") == x.end());
----
But since the elements aren't ordered, the output of:
[source,c++]
----
for(const map::value_type& i: x) {
std::cout<<i.first<<","<<i.second<<"\n";
}
----
can be in any order. For example, it might be:
[source]
----
two,2
one,1
three,3
----
To store an object in an unordered associative container requires both a
Storing an object in an unordered associative container requires both a
key equality function and a hash function. The default function objects in
the standard containers support a few basic types including integer types,
floating point types, pointer types, and the standard strings. Since
@@ -213,6 +95,3 @@ you have to extend Boost.Hash to support the type or use
your own custom equality predicates and hash functions. See the
<<hash_equality,Equality Predicates and Hash Functions>> section
for more details.
There are other differences, which are listed in the
<<comparison,Comparison with Associative Containers>> section.
+27 -5
View File
@@ -4,7 +4,7 @@
= Implementation Rationale
== Closed-addressing containers
== Closed-addressing Containers
`boost::unordered_[multi]set` and `boost::unordered_[multi]map`
adhere to the standard requirements for unordered associative
@@ -74,7 +74,7 @@ Since release 1.80.0, prime numbers are chosen for the number of buckets in
tandem with sophisticated modulo arithmetic. This removes the need for "mixing"
the result of the user's hash function as was used for release 1.79.0.
== Open-addresing containers
== Open-addresing Containers
The C++ standard specification of unordered associative containers impose
severe limitations on permissible implementations, the most important being
@@ -86,7 +86,7 @@ The design of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unord
guided by Peter Dimov's https://pdimov.github.io/articles/unordered_dev_plan.html[Development Plan for Boost.Unordered^].
We discuss here the most relevant principles.
=== Hash function
=== Hash Function
Given its rich functionality and cross-platform interoperability,
`boost::hash` remains the default hash function of open-addressing containers.
@@ -105,10 +105,10 @@ whereas in 32 bits _C_ = 0xE817FB2Du has been obtained from https://arxiv.org/ab
When using a hash function directly suitable for open addressing, post-mixing can be opted out by via a dedicated <<hash_traits_hash_is_avalanching,`hash_is_avalanching`>>trait.
`boost::hash` specializations for string types are marked as avalanching.
=== Platform interoperability
=== Platform Interoperability
The observable behavior of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unordered_flat_map`/`unordered_node_map` is deterministically
identical across different compilers as long as their ``std::size_type``s are the same size and the user-provided
identical across different compilers as long as their ``std::size_t``s are the same size and the user-provided
hash function and equality predicate are also interoperable
&#8212;this includes elements being ordered in exactly the same way for the same sequence of
operations.
@@ -117,3 +117,25 @@ Although the implementation internally uses SIMD technologies, such as https://e
and https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(NEON)[Neon^], when available,
this does not affect interoperatility. For instance, the behavior is the same
for Visual Studio on an x64-mode Intel CPU with SSE2 and for GCC on an IBM s390x without any supported SIMD technology.
== Concurrent Containers
The same data structure used by Boost.Unordered open-addressing containers has been chosen
also as the foundation of `boost::concurrent_flat_map`:
* Open-addressing is faster than closed-addressing alternatives, both in non-concurrent and
concurrent scenarios.
* Open-addressing layouts are eminently suitable for concurrent access and modification
with minimal locking. In particular, the metadata array can be used for implementations of
lookup that are lock-free up to the last step of actual element comparison.
* Layout compatibility with Boost.Unordered flat containers allows for fast transfer
of all elements between `boost::concurrent_flat_map` and `boost::unordered_flat_map`.
(This feature has not been implemented yet.)
=== Hash Function and Platform Interoperability
`boost::concurrent_flat_map` makes the same decisions and provides the same guarantees
as Boost.Unordered open-addressing containers with regards to
xref:#rationale_hash_function[hash function defaults] and
xref:#rationale_platform_interoperability[platform interoperability].
+1
View File
@@ -10,3 +10,4 @@ include::unordered_flat_map.adoc[]
include::unordered_flat_set.adoc[]
include::unordered_node_map.adoc[]
include::unordered_node_set.adoc[]
include::concurrent_flat_map.adoc[]
@@ -1,8 +1,99 @@
[#regular]
= Regular Containers
:idprefix: regular_
Boost.Unordered closed-addressing containers (`boost::unordered_set`, `boost::unordered_map`,
`boost::unordered_multiset` and `boost::unordered_multimap`) are fully conformant with the
C++ specification for unordered associative containers, so for those who know how to use
`std::unordered_set`, `std::unordered_map`, etc., their homonyms in Boost.Unordered are
drop-in replacements. The interface of open-addressing containers (`boost::unordered_node_set`,
`boost::unordered_node_map`, `boost::unordered_flat_set` and `boost::unordered_flat_map`)
is very similar, but they present some minor differences listed in the dedicated
xref:#compliance_open_addressing_containers[standard compliance section].
For readers without previous experience with hash containers but familiar
with normal associative containers (`std::set`, `std::map`,
`std::multiset` and `std::multimap`), Boost.Unordered containers are used in a similar manner:
[source,cpp]
----
typedef boost::unordered_map<std::string, int> map;
map x;
x["one"] = 1;
x["two"] = 2;
x["three"] = 3;
assert(x.at("one") == 1);
assert(x.find("missing") == x.end());
----
But since the elements aren't ordered, the output of:
[source,c++]
----
for(const map::value_type& i: x) {
std::cout<<i.first<<","<<i.second<<"\n";
}
----
can be in any order. For example, it might be:
[source]
----
two,2
one,1
three,3
----
There are other differences, which are listed in the
<<comparison,Comparison with Associative Containers>> section.
== Iterator Invalidation
It is not specified how member functions other than `rehash` and `reserve` affect
the bucket count, although `insert` can only invalidate iterators
when the insertion causes the container's load to be greater than the maximum allowed.
For most implementations this means that `insert` will only
change the number of buckets when this happens. Iterators can be
invalidated by calls to `insert`, `rehash` and `reserve`.
As for pointers and references,
they are never invalidated for node-based containers
(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`),
but they will be when rehashing occurs for
`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because
these containers store elements directly into their holding buckets, so
when allocating a new bucket array the elements must be transferred by means of move construction.
In a similar manner to using `reserve` for ``vector``s, it can be a good idea
to call `reserve` before inserting a large number of elements. This will get
the expensive rehashing out of the way and let you store iterators, safe in
the knowledge that they won't be invalidated. If you are inserting `n`
elements into container `x`, you could first call:
```
x.reserve(n);
```
Note:: `reserve(n)` reserves space for at least `n` elements, allocating enough buckets
so as to not exceed the maximum load factor.
+
Because the maximum load factor is defined as the number of elements divided by the total
number of available buckets, this function is logically equivalent to:
+
```
x.rehash(std::ceil(n / x.max_load_factor()))
```
+
See the <<unordered_map_rehash,reference for more details>> on the `rehash` function.
[#comparison]
:idprefix: comparison_
= Comparison with Associative Containers
== Comparison with Associative Containers
[caption=, title='Table {counter:table-counter} Interface differences']
[cols="1,1", frame=all, grid=rows]
@@ -32,7 +123,7 @@
|`iterator`, `const_iterator` are of at least the forward category.
|Iterators, pointers and references to the container's elements are never invalidated.
|<<buckets_iterator_invalidation,Iterators can be invalidated by calls to insert or rehash>>. +
|<<regular_iterator_invalidation,Iterators can be invalidated by calls to insert or rehash>>. +
**Node-based containers:** Pointers and references to the container's elements are never invalidated. +
**Flat containers:** Pointers and references to the container's elements are invalidated when rehashing occurs.
+179
View File
@@ -0,0 +1,179 @@
[#structures]
= Data Structures
:idprefix: structures_
== Closed-addressing Containers
++++
<style>
.imageblock > .title {
text-align: inherit;
}
</style>
++++
Boost.Unordered sports one of the fastest implementations of closed addressing, also commonly known as https://en.wikipedia.org/wiki/Hash_table#Separate_chaining[separate chaining]. An example figure representing the data structure is below:
[#img-bucket-groups,.text-center]
.A simple bucket group approach
image::bucket-groups.png[align=center]
An array of "buckets" is allocated and each bucket in turn points to its own individual linked list. This makes meeting the standard requirements of bucket iteration straight-forward. Unfortunately, iteration of the entire container is often times slow using this layout as each bucket must be examined for occupancy, yielding a time complexity of `O(bucket_count() + size())` when the standard requires complexity to be `O(size())`.
Canonical standard implementations will wind up looking like the diagram below:
[.text-center]
.The canonical standard approach
image::singly-linked.png[align=center,link=../diagrams/singly-linked.png,window=_blank]
It's worth noting that this approach is only used by pass:[libc++] and pass:[libstdc++]; the MSVC Dinkumware implementation uses a different one. A more detailed analysis of the standard containers can be found http://bannalia.blogspot.com/2013/10/implementation-of-c-unordered.html[here].
This unusually laid out data structure is chosen to make iteration of the entire container efficient by inter-connecting all of the nodes into a singly-linked list. One might also notice that buckets point to the node _before_ the start of the bucket's elements. This is done so that removing elements from the list can be done efficiently without introducing the need for a doubly-linked list. Unfortunately, this data structure introduces a guaranteed extra indirection. For example, to access the first element of a bucket, something like this must be done:
```c++
auto const idx = get_bucket_idx(hash_function(key));
node* p = buckets[idx]; // first load
node* n = p->next; // second load
if (n && is_in_bucket(n, idx)) {
value_type const& v = *n; // third load
// ...
}
```
With a simple bucket group layout, this is all that must be done:
```c++
auto const idx = get_bucket_idx(hash_function(key));
node* n = buckets[idx]; // first load
if (n) {
value_type const& v = *n; // second load
// ...
}
```
In practice, the extra indirection can have a dramatic performance impact to common operations such as `insert`, `find` and `erase`. But to keep iteration of the container fast, Boost.Unordered introduces a novel data structure, a "bucket group". A bucket group is a fixed-width view of a subsection of the buckets array. It contains a bitmask (a `std::size_t`) which it uses to track occupancy of buckets and contains two pointers so that it can form a doubly-linked list with non-empty groups. An example diagram is below:
[#img-fca-layout]
.The new layout used by Boost
image::fca.png[align=center]
Thus container-wide iteration is turned into traversing the non-empty bucket groups (an operation with constant time complexity) which reduces the time complexity back to `O(size())`. In total, a bucket group is only 4 words in size and it views `sizeof(std::size_t) * CHAR_BIT` buckets meaning that for all common implementations, there's only 4 bits of space overhead per bucket introduced by the bucket groups.
A more detailed description of Boost.Unordered's closed-addressing implementation is
given in an
https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article].
For more information on implementation rationale, read the
xref:#rationale_closed_addressing_containers[corresponding section].
== Open-addressing Containers
The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and
`boost:unordered_flat_set`/`unordered_node_set`.
[#img-foa-layout]
.Open-addressing layout used by Boost.Unordered.
image::foa.png[align=center]
As with all open-addressing containers, elements (or pointers to the element nodes in the case of
`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array.
This array is logically divided into 2^_n_^ _groups_ of 15 elements each.
In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^
16-byte words.
[#img-foa-metadata]
.Breakdown of a metadata word.
image::foa-metadata.png[align=center]
A metadata word is divided into 15 _h_~_i_~ bytes (one for each associated
bucket), and an _overflow byte_ (_ofw_ in the diagram). The value of _h_~_i_~ is:
- 0 if the corresponding bucket is empty.
- 1 to encode a special empty bucket called a _sentinel_, which is used internally to
stop iteration when the container has been fully traversed.
- If the bucket is occupied, a _reduced hash value_ obtained from the hash value of
the element.
When looking for an element with hash value _h_, SIMD technologies such as
https://en.wikipedia.org/wiki/SSE2[SSE2] and
https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)[Neon] allow us
to very quickly inspect the full metadata word and look for the reduced value of _h_ among all the
15 buckets with just a handful of CPU instructions: non-matching buckets can be
readily discarded, and those whose reduced hash value matches need be inspected via full
comparison with the corresponding element. If the looked-for element is not present,
the overflow byte is inspected:
- If the bit in the position _h_ mod 8 is zero, lookup terminates (and the
element is not present).
- If the bit is set to 1 (the group has been _overflowed_), further groups are
checked using https://en.wikipedia.org/wiki/Quadratic_probing[_quadratic probing_], and
the process is repeated.
Insertion is algorithmically similar: empty buckets are located using SIMD,
and when going past a full group its corresponding overflow bit is set to 1.
In architectures without SIMD support, the logical layout stays the same, but the metadata
word is codified using a technique we call _bit interleaving_: this layout allows us
to emulate SIMD with reasonably good performance using only standard arithmetic and
logical operations.
[#img-foa-metadata-interleaving]
.Bit-interleaved metadata word.
image::foa-metadata-interleaving.png[align=center]
A more detailed description of Boost.Unordered's open-addressing implementation is
given in an
https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article].
For more information on implementation rationale, read the
xref:#rationale_open_addresing_containers[corresponding section].
== Concurrent Containers
`boost::concurrent_flat_map` uses the basic
xref:#structures_open_addressing_containers[open-addressing layout] described above
augmented with synchronization mechanisms.
[#img-cfoa-layout]
.Concurrent open-addressing layout used by Boost.Unordered.
image::cfoa.png[align=center]
Two levels of synchronization are used:
* Container level: A read-write mutex is used to control access from any operation
to the container. Typically, such access is in read mode (that is, concurrent) even
for modifying operations, so for most practical purposes there is no thread
contention at this level. Access is only in write mode (blocking) when rehashing or
performing container-wide operations such as swapping or assignment.
* Group level: Each 15-slot group is equipped with an 8-byte word containing:
** A read-write spinlock for synchronized access to any element in the group.
** An atomic _insertion counter_ used for optimistic insertion as described
below.
By using atomic operations to access the group metadata, lookup is (group-level)
lock-free up to the point where an actual comparison needs to be done with an element
that has been previously SIMD-matched: only then it's the group's spinlock used.
Insertion uses the following _optimistic algorithm_:
* The value of the insertion counter for the initial group in the probe
sequence is locally recorded (let's call this value `c0`).
* Lookup is as described above. If lookup finds no equivalent element,
search for an available slot for insertion successively locks/unlocks
each group in the probing sequence.
* When an available slot is located, it is preemptively occupied (its
reduced hash value is set) and the insertion counter is atomically
incremented: if no other thread has incremented the counter during the
whole operation (which is checked by comparing with `c0`), then we're
good to go and complete the insertion, otherwise we roll back and start
over.
This algorithm has very low contention both at the lookup and actual
insertion phases in exchange for the possibility that computations have
to be started over if some other thread interferes in the process by
performing a succesful insertion beginning at the same group. In
practice, the start-over frequency is extremely small, measured in the range
of parts per million for some of our benchmarks.
For more information on implementation rationale, read the
xref:#rationale_concurrent_containers[corresponding section].
+11 -10
View File
@@ -1,5 +1,5 @@
[#unordered_flat_map]
== Class template unordered_flat_map
== Class Template unordered_flat_map
:idprefix: unordered_flat_map_
@@ -280,6 +280,7 @@ namespace boost {
unordered_flat_map<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class T, class H, class P, class A, class Predicate>
typename unordered_flat_map<K, T, H, P, A>::size_type
xref:#unordered_flat_map_erase_if[erase_if](unordered_flat_map<K, T, H, P, A>& c, Predicate pred);
@@ -859,7 +860,7 @@ void insert(std::initializer_list<value_type>);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load.
@@ -875,7 +876,7 @@ template<class K, class... Args>
std::pair<iterator, bool> try_emplace(K&& k, Args&&... args);
```
Inserts a new node into the container if there is no existing element with key `k` contained within it.
Inserts a new element into the container if there is no existing element with key `k` contained within it.
If there is an existing element with key `k` this function does nothing.
@@ -904,7 +905,7 @@ unlike xref:#unordered_flat_map_emplace[emplace], which simply forwards all argu
Can invalidate iterators pointers and references, but only if the insert causes the load to be greater than the maximum load.
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
--
@@ -920,7 +921,7 @@ template<class K, class... Args>
iterator try_emplace(const_iterator hint, K&& k, Args&&... args);
```
Inserts a new node into the container if there is no existing element with key `k` contained within it.
Inserts a new element into the container if there is no existing element with key `k` contained within it.
If there is an existing element with key `k` this function does nothing.
@@ -949,7 +950,7 @@ unlike xref:#unordered_flat_map_emplace_hint[emplace_hint], which simply forward
Can invalidate iterators pointers and references, but only if the insert causes the load to be greater than the maximum load.
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
--
@@ -1160,7 +1161,7 @@ template<class K>
[horizontal]
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1173,7 +1174,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1186,7 +1187,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1202,7 +1203,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+7 -6
View File
@@ -1,5 +1,5 @@
[#unordered_flat_set]
== Class template unordered_flat_set
== Class Template unordered_flat_set
:idprefix: unordered_flat_set_
@@ -234,6 +234,7 @@ namespace boost {
unordered_flat_set<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class T, class H, class P, class A, class Predicate>
typename unordered_flat_set<K, T, H, P, A>::size_type
xref:#unordered_flat_set_erase_if[erase_if](unordered_flat_set<K, T, H, P, A>& c, Predicate pred);
@@ -837,7 +838,7 @@ void insert(std::initializer_list<value_type>);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load.
@@ -971,7 +972,7 @@ template<class K>
[horizontal]
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -984,7 +985,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -997,7 +998,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1013,7 +1014,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+12 -11
View File
@@ -1,5 +1,5 @@
[#unordered_map]
== Class template unordered_map
== Class Template unordered_map
:idprefix: unordered_map_
@@ -286,6 +286,7 @@ namespace boost {
unordered_map<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class T, class H, class P, class A, class Predicate>
typename unordered_map<K, T, H, P, A>::size_type
xref:#unordered_map_erase_if[erase_if](unordered_map<K, T, H, P, A>& c, Predicate pred);
@@ -995,7 +996,7 @@ void insert(std::initializer_list<value_type>);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
+
@@ -1009,11 +1010,11 @@ template<class... Args>
std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);
template<class... Args>
std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
template <class K, class... Args>
template<class K, class... Args>
std::pair<iterator, bool> try_emplace(K&& k, Args&&... args)
```
Inserts a new node into the container if there is no existing element with key `k` contained within it.
Inserts a new element into the container if there is no existing element with key `k` contained within it.
If there is an existing element with key `k` this function does nothing.
@@ -1043,7 +1044,7 @@ Can invalidate iterators, but only if the insert causes the load factor to be gr
Pointers and references to elements are never invalidated.
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
If the compiler doesn't support variadic template arguments or rvalue references, this is emulated for up to `10` arguments, with no support for rvalue references or move semantics.
@@ -1062,7 +1063,7 @@ template<class K, class... Args>
iterator try_emplace(const_iterator hint, K&& k, Args&&... args);
```
Inserts a new node into the container if there is no existing element with key `k` contained within it.
Inserts a new element into the container if there is no existing element with key `k` contained within it.
If there is an existing element with key `k` this function does nothing.
@@ -1094,7 +1095,7 @@ Can invalidate iterators, but only if the insert causes the load factor to be gr
Pointers and references to elements are never invalidated.
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
If the compiler doesn't support variadic template arguments or rvalue references, this is emulated for up to `10` arguments, with no support for rvalue references or move semantics.
@@ -1466,7 +1467,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
+
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1479,7 +1480,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1492,7 +1493,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1508,7 +1509,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+7 -6
View File
@@ -1,5 +1,5 @@
[#unordered_multimap]
== Class template unordered_multimap
== Class Template unordered_multimap
:idprefix: unordered_multimap_
@@ -253,6 +253,7 @@ namespace boost {
unordered_multimap<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class T, class H, class P, class A, class Predicate>
typename unordered_multimap<K, T, H, P, A>::size_type
xref:#unordered_multimap_erase_if[erase_if](unordered_multimap<K, T, H, P, A>& c, Predicate pred);
@@ -941,7 +942,7 @@ void insert(std::initializer_list<value_type> il);
Inserts a range of elements into the container.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
+
@@ -1223,7 +1224,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
+
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1236,7 +1237,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1249,7 +1250,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1265,7 +1266,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+7 -6
View File
@@ -1,5 +1,5 @@
[#unordered_multiset]
== Class template unordered_multiset
== Class Template unordered_multiset
:idprefix: unordered_multiset_
@@ -244,6 +244,7 @@ namespace boost {
unordered_multiset<Key, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class H, class P, class A, class Predicate>
typename unordered_multiset<K, H, P, A>::size_type
xref:#unordered_multiset_erase_if[erase_if](unordered_multiset<K, H, P, A>& c, Predicate pred);
@@ -899,7 +900,7 @@ void insert(std::initializer_list<value_type> il);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
+
@@ -1181,7 +1182,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
+
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1194,7 +1195,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1207,7 +1208,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1223,7 +1224,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+11 -10
View File
@@ -1,5 +1,5 @@
[#unordered_node_map]
== Class template unordered_node_map
== Class Template unordered_node_map
:idprefix: unordered_node_map_
@@ -284,6 +284,7 @@ namespace boost {
unordered_node_map<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class T, class H, class P, class A, class Predicate>
typename unordered_node_map<K, T, H, P, A>::size_type
xref:#unordered_node_map_erase_if[erase_if](unordered_node_map<K, T, H, P, A>& c, Predicate pred);
@@ -893,7 +894,7 @@ void insert(std::initializer_list<value_type>);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
@@ -945,7 +946,7 @@ template<class K, class... Args>
std::pair<iterator, bool> try_emplace(K&& k, Args&&... args);
```
Inserts a new node into the container if there is no existing element with key `k` contained within it.
Inserts a new element into the container if there is no existing element with key `k` contained within it.
If there is an existing element with key `k` this function does nothing.
@@ -974,7 +975,7 @@ unlike xref:#unordered_node_map_emplace[emplace], which simply forwards all argu
Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
--
@@ -990,7 +991,7 @@ template<class K, class... Args>
iterator try_emplace(const_iterator hint, K&& k, Args&&... args);
```
Inserts a new node into the container if there is no existing element with key `k` contained within it.
Inserts a new element into the container if there is no existing element with key `k` contained within it.
If there is an existing element with key `k` this function does nothing.
@@ -1019,7 +1020,7 @@ unlike xref:#unordered_node_map_emplace_hint[emplace_hint], which simply forward
Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
--
@@ -1258,7 +1259,7 @@ template<class K>
[horizontal]
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1271,7 +1272,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1284,7 +1285,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1300,7 +1301,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+7 -6
View File
@@ -1,5 +1,5 @@
[#unordered_node_set]
== Class template unordered_node_set
== Class Template unordered_node_set
:idprefix: unordered_node_set_
@@ -238,6 +238,7 @@ namespace boost {
unordered_node_set<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class T, class H, class P, class A, class Predicate>
typename unordered_node_set<K, T, H, P, A>::size_type
xref:#unordered_node_set_erase_if[erase_if](unordered_node_set<K, T, H, P, A>& c, Predicate pred);
@@ -874,7 +875,7 @@ void insert(std::initializer_list<value_type>);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
@@ -1072,7 +1073,7 @@ template<class K>
[horizontal]
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1085,7 +1086,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1098,7 +1099,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1114,7 +1115,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
+7 -6
View File
@@ -1,5 +1,5 @@
[#unordered_set]
== Class template unordered_set
== Class Template unordered_set
:idprefix: unordered_set_
@@ -245,6 +245,7 @@ namespace boost {
unordered_set<Key, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
// Erasure
template<class K, class H, class P, class A, class Predicate>
typename unordered_set<K, H, P, A>::size_type
xref:#unordered_set_erase_if[erase_if](unordered_set<K, H, P, A>& c, Predicate pred);
@@ -959,7 +960,7 @@ void insert(std::initializer_list<value_type>);
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
[horizontal]
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
+
@@ -1248,7 +1249,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
+
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1261,7 +1262,7 @@ template<class K>
[horizontal]
Returns:;; The number of elements with key equivalent to `k`.
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1274,7 +1275,7 @@ template<class K>
[horizontal]
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -1290,7 +1291,7 @@ template<class K>
[horizontal]
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
---
@@ -0,0 +1,869 @@
/* Fast open-addressing concurrent hash table.
*
* Copyright 2023 Christian Mazakas.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
/* Reference:
* https://github.com/joaquintides/concurrent_hashmap_api#proposed-synopsis
*/
#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
#include <boost/unordered/detail/foa/concurrent_table.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/container_hash/hash.hpp>
#include <boost/core/allocator_access.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <boost/type_traits/type_identity.hpp>
#include <functional>
#include <type_traits>
#include <utility>
#define BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) \
static_assert(boost::unordered::detail::is_invocable<F, value_type&>::value, \
"The provided Callable must be invocable with `value_type&`");
#define BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) \
static_assert( \
boost::unordered::detail::is_invocable<F, value_type const&>::value, \
"The provided Callable must be invocable with `value_type const&`");
#if BOOST_CXX_VERSION >= 202002L
#define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \
static_assert(!std::is_base_of<std::execution::parallel_unsequenced_policy, \
ExecPolicy>::value, \
"ExecPolicy must be sequenced."); \
static_assert( \
!std::is_base_of<std::execution::unsequenced_policy, ExecPolicy>::value, \
"ExecPolicy must be sequenced.");
#else
#define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \
static_assert(!std::is_base_of<std::execution::parallel_unsequenced_policy, \
ExecPolicy>::value, \
"ExecPolicy must be sequenced.");
#endif
#define BOOST_UNORDERED_COMMA ,
#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \
mp11::mp_back<mp11::mp_list<Arg BOOST_UNORDERED_COMMA Args> >
#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args))
#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \
BOOST_UNORDERED_LAST_ARG(Arg, Args))
namespace boost {
namespace unordered {
namespace detail {
template <class F, class... Args>
struct is_invocable
: std::is_constructible<std::function<void(Args...)>,
std::reference_wrapper<typename std::remove_reference<F>::type> >
{
};
template <class Key, class T> struct concurrent_map_types
{
using key_type = Key;
using raw_key_type = typename std::remove_const<Key>::type;
using raw_mapped_type = typename std::remove_const<T>::type;
using init_type = std::pair<raw_key_type, raw_mapped_type>;
using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;
using value_type = std::pair<Key const, T>;
using element_type = value_type;
static value_type& value_from(element_type& x) { return x; }
template <class K, class V>
static raw_key_type const& extract(std::pair<K, V> const& kv)
{
return kv.first;
}
static moved_type move(init_type& x)
{
return {std::move(x.first), std::move(x.second)};
}
static moved_type move(element_type& x)
{
// TODO: we probably need to launder here
return {std::move(const_cast<raw_key_type&>(x.first)),
std::move(const_cast<raw_mapped_type&>(x.second))};
}
template <class A, class... Args>
static void construct(A& al, init_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A> static void destroy(A& al, init_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
};
} // namespace detail
template <class Key, class T, class Hash, class Pred, class Allocator>
class concurrent_flat_map
{
private:
template <class Key2, class T2, class Hash2, class Pred2,
class Allocator2>
friend class concurrent_flat_map;
using type_policy = detail::concurrent_map_types<Key, T>;
detail::foa::concurrent_table<type_policy, Hash, Pred, Allocator> table_;
template <class K, class V, class H, class KE, class A>
bool friend operator==(concurrent_flat_map<K, V, H, KE, A> const& lhs,
concurrent_flat_map<K, V, H, KE, A> const& rhs);
public:
using key_type = Key;
using mapped_type = T;
using value_type = typename type_policy::value_type;
using init_type = typename type_policy::init_type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using hasher = typename boost::type_identity<Hash>::type;
using key_equal = typename boost::type_identity<Pred>::type;
using allocator_type = typename boost::type_identity<Allocator>::type;
using reference = value_type&;
using const_reference = value_type const&;
using pointer = typename boost::allocator_pointer<allocator_type>::type;
using const_pointer =
typename boost::allocator_const_pointer<allocator_type>::type;
concurrent_flat_map()
: concurrent_flat_map(detail::foa::default_bucket_count)
{
}
explicit concurrent_flat_map(size_type n, const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type())
: table_(n, hf, eql, a)
{
}
template <class InputIterator>
concurrent_flat_map(InputIterator f, InputIterator l,
size_type n = detail::foa::default_bucket_count,
const hasher& hf = hasher(), const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type())
: table_(n, hf, eql, a)
{
this->insert(f, l);
}
concurrent_flat_map(concurrent_flat_map const& rhs)
: table_(rhs.table_,
boost::allocator_select_on_container_copy_construction(
rhs.get_allocator()))
{
}
concurrent_flat_map(concurrent_flat_map&& rhs)
: table_(std::move(rhs.table_))
{
}
template <class InputIterator>
concurrent_flat_map(InputIterator f, InputIterator l, allocator_type a)
: concurrent_flat_map(f, l, 0, hasher(), key_equal(), a)
{
}
explicit concurrent_flat_map(allocator_type a)
: table_(detail::foa::default_bucket_count, hasher(), key_equal(), a)
{
}
concurrent_flat_map(concurrent_flat_map const& rhs, allocator_type a)
: table_(rhs.table_, a)
{
}
concurrent_flat_map(concurrent_flat_map&& rhs, allocator_type a)
: table_(std::move(rhs.table_), a)
{
}
concurrent_flat_map(std::initializer_list<value_type> il,
size_type n = detail::foa::default_bucket_count,
const hasher& hf = hasher(), const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type())
: concurrent_flat_map(n, hf, eql, a)
{
this->insert(il.begin(), il.end());
}
concurrent_flat_map(size_type n, const allocator_type& a)
: concurrent_flat_map(n, hasher(), key_equal(), a)
{
}
concurrent_flat_map(
size_type n, const hasher& hf, const allocator_type& a)
: concurrent_flat_map(n, hf, key_equal(), a)
{
}
template <typename InputIterator>
concurrent_flat_map(
InputIterator f, InputIterator l, size_type n, const allocator_type& a)
: concurrent_flat_map(f, l, n, hasher(), key_equal(), a)
{
}
template <typename InputIterator>
concurrent_flat_map(InputIterator f, InputIterator l, size_type n,
const hasher& hf, const allocator_type& a)
: concurrent_flat_map(f, l, n, hf, key_equal(), a)
{
}
concurrent_flat_map(
std::initializer_list<value_type> il, const allocator_type& a)
: concurrent_flat_map(
il, detail::foa::default_bucket_count, hasher(), key_equal(), a)
{
}
concurrent_flat_map(std::initializer_list<value_type> il, size_type n,
const allocator_type& a)
: concurrent_flat_map(il, n, hasher(), key_equal(), a)
{
}
concurrent_flat_map(std::initializer_list<value_type> il, size_type n,
const hasher& hf, const allocator_type& a)
: concurrent_flat_map(il, n, hf, key_equal(), a)
{
}
~concurrent_flat_map() = default;
concurrent_flat_map& operator=(concurrent_flat_map const& rhs)
{
table_ = rhs.table_;
return *this;
}
concurrent_flat_map& operator=(concurrent_flat_map&& rhs)
noexcept(boost::allocator_is_always_equal<Allocator>::type::value ||
boost::allocator_propagate_on_container_move_assignment<
Allocator>::type::value)
{
table_ = std::move(rhs.table_);
return *this;
}
concurrent_flat_map& operator=(std::initializer_list<value_type> ilist)
{
table_ = ilist;
return *this;
}
/// Capacity
///
size_type size() const noexcept { return table_.size(); }
BOOST_ATTRIBUTE_NODISCARD bool empty() const noexcept
{
return size() == 0;
}
template <class F>
BOOST_FORCEINLINE size_type visit(key_type const& k, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
return table_.visit(k, f);
}
template <class F>
BOOST_FORCEINLINE size_type visit(key_type const& k, F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.visit(k, f);
}
template <class F>
BOOST_FORCEINLINE size_type cvisit(key_type const& k, F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.visit(k, f);
}
template <class K, class F>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
visit(K&& k, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
return table_.visit(std::forward<K>(k), f);
}
template <class K, class F>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
visit(K&& k, F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.visit(std::forward<K>(k), f);
}
template <class K, class F>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
cvisit(K&& k, F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.visit(std::forward<K>(k), f);
}
template <class F> size_type visit_all(F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
return table_.visit_all(f);
}
template <class F> size_type visit_all(F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.visit_all(f);
}
template <class F> size_type cvisit_all(F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.cvisit_all(f);
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template <class ExecPolicy, class F>
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
void>::type
visit_all(ExecPolicy&& p, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
table_.visit_all(p, f);
}
template <class ExecPolicy, class F>
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
void>::type
visit_all(ExecPolicy&& p, F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
table_.visit_all(p, f);
}
template <class ExecPolicy, class F>
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
void>::type
cvisit_all(ExecPolicy&& p, F f) const
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
table_.cvisit_all(p, f);
}
#endif
/// Modifiers
///
template <class Ty>
BOOST_FORCEINLINE auto insert(Ty&& value)
-> decltype(table_.insert(std::forward<Ty>(value)))
{
return table_.insert(std::forward<Ty>(value));
}
BOOST_FORCEINLINE bool insert(init_type&& obj)
{
return table_.insert(std::move(obj));
}
template <class InputIterator>
void insert(InputIterator begin, InputIterator end)
{
for (auto pos = begin; pos != end; ++pos) {
table_.insert(*pos);
}
}
void insert(std::initializer_list<value_type> ilist)
{
this->insert(ilist.begin(), ilist.end());
}
template <class M>
BOOST_FORCEINLINE bool insert_or_assign(key_type const& k, M&& obj)
{
return table_.try_emplace_or_visit(k, std::forward<M>(obj),
[&](value_type& m) { m.second = std::forward<M>(obj); });
}
template <class M>
BOOST_FORCEINLINE bool insert_or_assign(key_type&& k, M&& obj)
{
return table_.try_emplace_or_visit(std::move(k), std::forward<M>(obj),
[&](value_type& m) { m.second = std::forward<M>(obj); });
}
template <class K, class M>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, bool>::type
insert_or_assign(K&& k, M&& obj)
{
return table_.try_emplace_or_visit(std::forward<K>(k),
std::forward<M>(obj),
[&](value_type& m) { m.second = std::forward<M>(obj); });
}
template <class Ty, class F>
BOOST_FORCEINLINE auto insert_or_visit(Ty&& value, F f)
-> decltype(table_.insert_or_visit(std::forward<Ty>(value), f))
{
return table_.insert_or_visit(std::forward<Ty>(value), f);
}
template <class F>
BOOST_FORCEINLINE bool insert_or_visit(init_type&& obj, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
return table_.insert_or_visit(std::move(obj), f);
}
template <class InputIterator, class F>
void insert_or_visit(InputIterator first, InputIterator last, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
for (; first != last; ++first) {
table_.insert_or_visit(*first, f);
}
}
template <class F>
void insert_or_visit(std::initializer_list<value_type> ilist, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
this->insert_or_visit(ilist.begin(), ilist.end(), f);
}
template <class Ty, class F>
BOOST_FORCEINLINE auto insert_or_cvisit(Ty&& value, F f)
-> decltype(table_.insert_or_cvisit(std::forward<Ty>(value), f))
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.insert_or_cvisit(std::forward<Ty>(value), f);
}
template <class F>
BOOST_FORCEINLINE bool insert_or_cvisit(init_type&& obj, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
return table_.insert_or_cvisit(std::move(obj), f);
}
template <class InputIterator, class F>
void insert_or_cvisit(InputIterator first, InputIterator last, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
for (; first != last; ++first) {
table_.insert_or_cvisit(*first, f);
}
}
template <class F>
void insert_or_cvisit(std::initializer_list<value_type> ilist, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
this->insert_or_visit(ilist.begin(), ilist.end(), f);
}
template <class... Args> BOOST_FORCEINLINE bool emplace(Args&&... args)
{
return table_.emplace(std::forward<Args>(args)...);
}
template <class Arg, class... Args>
BOOST_FORCEINLINE bool emplace_or_visit(Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
return table_.emplace_or_visit(
std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class Arg, class... Args>
BOOST_FORCEINLINE bool emplace_or_cvisit(Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
return table_.emplace_or_cvisit(
std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class... Args>
BOOST_FORCEINLINE bool try_emplace(key_type const& k, Args&&... args)
{
return table_.try_emplace(k, std::forward<Args>(args)...);
}
template <class... Args>
BOOST_FORCEINLINE bool try_emplace(key_type&& k, Args&&... args)
{
return table_.try_emplace(std::move(k), std::forward<Args>(args)...);
}
template <class K, class... Args>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, bool>::type
try_emplace(K&& k, Args&&... args)
{
return table_.try_emplace(
std::forward<K>(k), std::forward<Args>(args)...);
}
template <class Arg, class... Args>
BOOST_FORCEINLINE bool try_emplace_or_visit(
key_type const& k, Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
return table_.try_emplace_or_visit(
k, std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class Arg, class... Args>
BOOST_FORCEINLINE bool try_emplace_or_cvisit(
key_type const& k, Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
return table_.try_emplace_or_cvisit(
k, std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class Arg, class... Args>
BOOST_FORCEINLINE bool try_emplace_or_visit(
key_type&& k, Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
return table_.try_emplace_or_visit(
std::move(k), std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class Arg, class... Args>
BOOST_FORCEINLINE bool try_emplace_or_cvisit(
key_type&& k, Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
return table_.try_emplace_or_cvisit(
std::move(k), std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class K, class Arg, class... Args>
BOOST_FORCEINLINE bool try_emplace_or_visit(
K&& k, Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...)
return table_.try_emplace_or_visit(std::forward<K>(k),
std::forward<Arg>(arg), std::forward<Args>(args)...);
}
template <class K, class Arg, class... Args>
BOOST_FORCEINLINE bool try_emplace_or_cvisit(
K&& k, Arg&& arg, Args&&... args)
{
BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...)
return table_.try_emplace_or_cvisit(std::forward<K>(k),
std::forward<Arg>(arg), std::forward<Args>(args)...);
}
BOOST_FORCEINLINE size_type erase(key_type const& k)
{
return table_.erase(k);
}
template <class K>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
erase(K&& k)
{
return table_.erase(std::forward<K>(k));
}
template <class F>
BOOST_FORCEINLINE size_type erase_if(key_type const& k, F f)
{
return table_.erase_if(k, f);
}
template <class K, class F>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value &&
!detail::is_execution_policy<K>::value,
size_type>::type
erase_if(K&& k, F f)
{
return table_.erase_if(std::forward<K>(k), f);
}
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
template <class ExecPolicy, class F>
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
void>::type
erase_if(ExecPolicy&& p, F f)
{
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
table_.erase_if(p, f);
}
#endif
template <class F> size_type erase_if(F f) { return table_.erase_if(f); }
void swap(concurrent_flat_map& other) noexcept(
boost::allocator_is_always_equal<Allocator>::type::value ||
boost::allocator_propagate_on_container_swap<Allocator>::type::value)
{
return table_.swap(other.table_);
}
void clear() noexcept { table_.clear(); }
template <typename H2, typename P2>
size_type merge(concurrent_flat_map<Key, T, H2, P2, Allocator>& x)
{
BOOST_ASSERT(get_allocator() == x.get_allocator());
return table_.merge(x.table_);
}
template <typename H2, typename P2>
size_type merge(concurrent_flat_map<Key, T, H2, P2, Allocator>&& x)
{
return merge(x);
}
BOOST_FORCEINLINE size_type count(key_type const& k) const
{
return table_.count(k);
}
template <class K>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
count(K const& k)
{
return table_.count(k);
}
BOOST_FORCEINLINE bool contains(key_type const& k) const
{
return table_.contains(k);
}
template <class K>
BOOST_FORCEINLINE typename std::enable_if<
detail::are_transparent<K, hasher, key_equal>::value, bool>::type
contains(K const& k) const
{
return table_.contains(k);
}
/// Hash Policy
///
size_type bucket_count() const noexcept { return table_.capacity(); }
float load_factor() const noexcept { return table_.load_factor(); }
float max_load_factor() const noexcept
{
return table_.max_load_factor();
};
void max_load_factor(float) {}
size_type max_load() const noexcept { return table_.max_load(); }
void rehash(size_type n) { table_.rehash(n); }
void reserve(size_type n) { table_.reserve(n); }
/// Observers
///
allocator_type get_allocator() const noexcept
{
return table_.get_allocator();
}
hasher hash_function() const { return table_.hash_function(); }
key_equal key_eq() const { return table_.key_eq(); }
};
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
bool operator==(
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
{
return lhs.table_ == rhs.table_;
}
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
bool operator!=(
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
{
return !(lhs == rhs);
}
template <class Key, class T, class Hash, class Pred, class Alloc>
void swap(concurrent_flat_map<Key, T, Hash, Pred, Alloc>& x,
concurrent_flat_map<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)))
{
x.swap(y);
}
template <class K, class T, class H, class P, class A, class Predicate>
typename concurrent_flat_map<K, T, H, P, A>::size_type erase_if(
concurrent_flat_map<K, T, H, P, A>& c, Predicate pred)
{
return c.erase_if(pred);
}
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
template <class InputIterator,
class Hash =
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
class Pred =
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
class Allocator = std::allocator<
boost::unordered::detail::iter_to_alloc_t<InputIterator> >,
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
class = boost::enable_if_t<detail::is_pred_v<Pred> >,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(InputIterator, InputIterator,
std::size_t = boost::unordered::detail::foa::default_bucket_count,
Hash = Hash(), Pred = Pred(), Allocator = Allocator())
-> concurrent_flat_map<
boost::unordered::detail::iter_key_t<InputIterator>,
boost::unordered::detail::iter_val_t<InputIterator>, Hash, Pred,
Allocator>;
template <class Key, class T,
class Hash = boost::hash<boost::remove_const_t<Key> >,
class Pred = std::equal_to<boost::remove_const_t<Key> >,
class Allocator = std::allocator<std::pair<const Key, T> >,
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
class = boost::enable_if_t<detail::is_pred_v<Pred> >,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >,
std::size_t = boost::unordered::detail::foa::default_bucket_count,
Hash = Hash(), Pred = Pred(), Allocator = Allocator())
-> concurrent_flat_map<boost::remove_const_t<Key>, T, Hash, Pred,
Allocator>;
template <class InputIterator, class Allocator,
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(InputIterator, InputIterator, std::size_t, Allocator)
-> concurrent_flat_map<
boost::unordered::detail::iter_key_t<InputIterator>,
boost::unordered::detail::iter_val_t<InputIterator>,
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
Allocator>;
template <class InputIterator, class Allocator,
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(InputIterator, InputIterator, Allocator)
-> concurrent_flat_map<
boost::unordered::detail::iter_key_t<InputIterator>,
boost::unordered::detail::iter_val_t<InputIterator>,
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
Allocator>;
template <class InputIterator, class Hash, class Allocator,
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(
InputIterator, InputIterator, std::size_t, Hash, Allocator)
-> concurrent_flat_map<
boost::unordered::detail::iter_key_t<InputIterator>,
boost::unordered::detail::iter_val_t<InputIterator>, Hash,
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
Allocator>;
template <class Key, class T, class Allocator,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >, std::size_t,
Allocator) -> concurrent_flat_map<boost::remove_const_t<Key>, T,
boost::hash<boost::remove_const_t<Key> >,
std::equal_to<boost::remove_const_t<Key> >, Allocator>;
template <class Key, class T, class Allocator,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >, Allocator)
-> concurrent_flat_map<boost::remove_const_t<Key>, T,
boost::hash<boost::remove_const_t<Key> >,
std::equal_to<boost::remove_const_t<Key> >, Allocator>;
template <class Key, class T, class Hash, class Allocator,
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >, std::size_t,
Hash, Allocator) -> concurrent_flat_map<boost::remove_const_t<Key>, T,
Hash, std::equal_to<boost::remove_const_t<Key> >, Allocator>;
#endif
} // namespace unordered
using unordered::concurrent_flat_map;
} // namespace boost
#undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE
#undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE
#undef BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY
#undef BOOST_UNORDERED_COMMA
#undef BOOST_UNORDERED_LAST_ARG
#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE
#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE
#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
@@ -0,0 +1,58 @@
/* Fast open-addressing concurrent hash table.
*
* Copyright 2023 Christian Mazakas.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
/* Reference:
* https://github.com/joaquintides/concurrent_hashmap_api#proposed-synopsis
*/
#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP
#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP
#include <boost/container_hash/hash_fwd.hpp>
#include <functional>
#include <memory>
namespace boost {
namespace unordered {
template <class Key, class T, class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Allocator = std::allocator<std::pair<Key const, T> > >
class concurrent_flat_map;
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
bool operator==(
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
bool operator!=(
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);
template <class Key, class T, class Hash, class Pred, class Alloc>
void swap(concurrent_flat_map<Key, T, Hash, Pred, Alloc>& x,
concurrent_flat_map<Key, T, Hash, Pred, Alloc>& y)
noexcept(noexcept(x.swap(y)));
template <class K, class T, class H, class P, class A, class Predicate>
typename concurrent_flat_map<K, T, H, P, A>::size_type erase_if(
concurrent_flat_map<K, T, H, P, A>& c, Predicate pred);
} // namespace unordered
using boost::unordered::concurrent_flat_map;
using boost::unordered::swap;
using boost::unordered::operator==;
using boost::unordered::operator!=;
} // namespace boost
#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
/* Copyright 2023 Joaquin M Lopez Munoz.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
#include <boost/config.hpp>
#if defined(BOOST_GCC)
#if !defined(BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW)
/* GCC's -Wshadow triggers at scenarios like this:
*
* struct foo{};
* template<typename Base>
* struct derived:Base
* {
* void f(){int foo;}
* };
*
* derived<foo>x;
* x.f(); // declaration of "foo" in derived::f shadows base type "foo"
*
* This makes shadowing warnings unavoidable in general when a class template
* derives from user-provided classes, as is the case with foa::table_core
* deriving from empty_value.
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#else
#pragma GCC diagnostic pop
#endif
#endif
@@ -0,0 +1,11 @@
/* Copyright 2023 Joaquin M Lopez Munoz.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
#define BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
#undef BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW
@@ -0,0 +1,187 @@
#ifndef BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
#define BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
// Copyright 2023 Peter Dimov
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/core/yield_primitives.hpp>
#include <atomic>
#include <cstdint>
namespace boost{
namespace unordered{
namespace detail{
namespace foa{
class rw_spinlock
{
private:
// bit 31: locked exclusive
// bit 30: writer pending
// bit 29..0: reader lock count
static constexpr std::uint32_t locked_exclusive_mask = 1u << 31; // 0x8000'0000
static constexpr std::uint32_t writer_pending_mask = 1u << 30; // 0x4000'0000
static constexpr std::uint32_t reader_lock_count_mask = writer_pending_mask - 1; // 0x3FFF'FFFF
std::atomic<std::uint32_t> state_ = {};
private:
// number of times to spin before sleeping
static constexpr int spin_count = 24576;
public:
bool try_lock_shared() noexcept
{
std::uint32_t st = state_.load( std::memory_order_relaxed );
if( st >= reader_lock_count_mask )
{
// either bit 31 set, bit 30 set, or reader count is max
return false;
}
std::uint32_t newst = st + 1;
return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed );
}
void lock_shared() noexcept
{
for( ;; )
{
for( int k = 0; k < spin_count; ++k )
{
std::uint32_t st = state_.load( std::memory_order_relaxed );
if( st < reader_lock_count_mask )
{
std::uint32_t newst = st + 1;
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
}
boost::core::sp_thread_pause();
}
boost::core::sp_thread_sleep();
}
}
void unlock_shared() noexcept
{
// pre: locked shared, not locked exclusive
state_.fetch_sub( 1, std::memory_order_release );
// if the writer pending bit is set, there's a writer waiting
// let it acquire the lock; it will clear the bit on unlock
}
bool try_lock() noexcept
{
std::uint32_t st = state_.load( std::memory_order_relaxed );
if( st & locked_exclusive_mask )
{
// locked exclusive
return false;
}
if( st & reader_lock_count_mask )
{
// locked shared
return false;
}
std::uint32_t newst = locked_exclusive_mask;
return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed );
}
void lock() noexcept
{
for( ;; )
{
for( int k = 0; k < spin_count; ++k )
{
std::uint32_t st = state_.load( std::memory_order_relaxed );
if( st & locked_exclusive_mask )
{
// locked exclusive, spin
}
else if( ( st & reader_lock_count_mask ) == 0 )
{
// not locked exclusive, not locked shared, try to lock
std::uint32_t newst = locked_exclusive_mask;
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
}
else if( st & writer_pending_mask )
{
// writer pending bit already set, nothing to do
}
else
{
// locked shared, set writer pending bit
std::uint32_t newst = st | writer_pending_mask;
state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed );
}
boost::core::sp_thread_pause();
}
// clear writer pending bit before going to sleep
{
std::uint32_t st = state_.load( std::memory_order_relaxed );
for( ;; )
{
if( st & locked_exclusive_mask )
{
// locked exclusive, nothing to do
break;
}
else if( ( st & reader_lock_count_mask ) == 0 )
{
// lock free, try to take it
std::uint32_t newst = locked_exclusive_mask;
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
}
else if( ( st & writer_pending_mask ) == 0 )
{
// writer pending bit already clear, nothing to do
break;
}
else
{
// clear writer pending bit
std::uint32_t newst = st & ~writer_pending_mask;
if( state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed ) ) break;
}
}
}
boost::core::sp_thread_sleep();
}
}
void unlock() noexcept
{
// pre: locked exclusive, not locked shared
state_.store( 0, std::memory_order_release );
}
};
} /* namespace foa */
} /* namespace detail */
} /* namespace unordered */
} /* namespace boost */
#endif // BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
@@ -0,0 +1,513 @@
/* Fast open-addressing hash table.
*
* Copyright 2022-2023 Joaquin M Lopez Munoz.
* Copyright 2023 Christian Mazakas.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
#ifndef BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP
#define BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP
#include <boost/assert.hpp>
#include <boost/config.hpp>
#include <boost/config/workaround.hpp>
#include <boost/unordered/detail/foa/core.hpp>
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
namespace boost{
namespace unordered{
namespace detail{
namespace foa{
/* use plain integrals for group metadata storage */
template<typename Integral>
struct plain_integral
{
operator Integral()const{return n;}
void operator=(Integral m){n=m;}
#if BOOST_WORKAROUND(BOOST_GCC,>=50000 && BOOST_GCC<60000)
void operator|=(Integral m){n=static_cast<Integral>(n|m);}
void operator&=(Integral m){n=static_cast<Integral>(n&m);}
#else
void operator|=(Integral m){n|=m;}
void operator&=(Integral m){n&=m;}
#endif
Integral n;
};
struct plain_size_control
{
std::size_t ml;
std::size_t size;
};
template<typename,typename,typename,typename>
class table;
/* table_iterator keeps two pointers:
*
* - A pointer p to the element slot.
* - A pointer pc to the n-th byte of the associated group metadata, where n
* is the position of the element in the group.
*
* A simpler solution would have been to keep a pointer p to the element, a
* pointer pg to the group, and the position n, but that would increase
* sizeof(table_iterator) by 4/8 bytes. In order to make this compact
* representation feasible, it is required that group objects are aligned
* to their size, so that we can recover pg and n as
*
* - n = pc%sizeof(group)
* - pg = pc-n
*
* (for explanatory purposes pg and pc are treated above as if they were memory
* addresses rather than pointers).
*
* p = nullptr is conventionally used to mark end() iterators.
*/
/* internal conversion from const_iterator to iterator */
struct const_iterator_cast_tag{};
template<typename TypePolicy,typename Group,bool Const>
class table_iterator
{
using type_policy=TypePolicy;
using table_element_type=typename type_policy::element_type;
using group_type=Group;
static constexpr auto N=group_type::N;
static constexpr auto regular_layout=group_type::regular_layout;
public:
using difference_type=std::ptrdiff_t;
using value_type=typename type_policy::value_type;
using pointer=
typename std::conditional<Const,value_type const*,value_type*>::type;
using reference=
typename std::conditional<Const,value_type const&,value_type&>::type;
using iterator_category=std::forward_iterator_tag;
using element_type=
typename std::conditional<Const,value_type const,value_type>::type;
table_iterator()=default;
template<bool Const2,typename std::enable_if<!Const2>::type* =nullptr>
table_iterator(const table_iterator<TypePolicy,Group,Const2>& x):
pc{x.pc},p{x.p}{}
table_iterator(
const_iterator_cast_tag, const table_iterator<TypePolicy,Group,true>& x):
pc{x.pc},p{x.p}{}
inline reference operator*()const noexcept
{return type_policy::value_from(*p);}
inline pointer operator->()const noexcept
{return std::addressof(type_policy::value_from(*p));}
inline table_iterator& operator++()noexcept{increment();return *this;}
inline table_iterator operator++(int)noexcept
{auto x=*this;increment();return x;}
friend inline bool operator==(
const table_iterator& x,const table_iterator& y)
{return x.p==y.p;}
friend inline bool operator!=(
const table_iterator& x,const table_iterator& y)
{return !(x==y);}
private:
template<typename,typename,bool> friend class table_iterator;
template<typename,typename,typename,typename> friend class table;
table_iterator(Group* pg,std::size_t n,const table_element_type* p_):
pc{reinterpret_cast<unsigned char*>(const_cast<group_type*>(pg))+n},
p{const_cast<table_element_type*>(p_)}
{}
inline void increment()noexcept
{
BOOST_ASSERT(p!=nullptr);
increment(std::integral_constant<bool,regular_layout>{});
}
inline void increment(std::true_type /* regular layout */)noexcept
{
for(;;){
++p;
if(reinterpret_cast<uintptr_t>(pc)%sizeof(group_type)==N-1){
pc+=sizeof(group_type)-(N-1);
break;
}
++pc;
if(!group_type::is_occupied(pc))continue;
if(BOOST_UNLIKELY(group_type::is_sentinel(pc)))p=nullptr;
return;
}
for(;;){
int mask=reinterpret_cast<group_type*>(pc)->match_occupied();
if(mask!=0){
auto n=unchecked_countr_zero(mask);
if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc)->is_sentinel(n))){
p=nullptr;
}
else{
pc+=n;
p+=n;
}
return;
}
pc+=sizeof(group_type);
p+=N;
}
}
inline void increment(std::false_type /* interleaved */)noexcept
{
std::size_t n0=reinterpret_cast<uintptr_t>(pc)%sizeof(group_type);
pc-=n0;
int mask=(
reinterpret_cast<group_type*>(pc)->match_occupied()>>(n0+1))<<(n0+1);
if(!mask){
do{
pc+=sizeof(group_type);
p+=N;
}
while((mask=reinterpret_cast<group_type*>(pc)->match_occupied())==0);
}
auto n=unchecked_countr_zero(mask);
if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc)->is_sentinel(n))){
p=nullptr;
}
else{
pc+=n;
p-=n0;
p+=n;
}
}
unsigned char *pc=nullptr;
table_element_type *p=nullptr;
};
/* foa::table interface departs in a number of ways from that of C++ unordered
* associative containers because it's not for end-user consumption
* (boost::unordered_(flat|node)_(map|set) wrappers complete it as
* appropriate).
*
* The table supports two main modes of operation: flat and node-based. In the
* flat case, buckets directly store elements. For node-based, buckets store
* pointers to individually heap-allocated elements.
*
* For both flat and node-based:
*
* - begin() is not O(1).
* - No bucket API.
* - Load factor is fixed and can't be set by the user.
*
* For flat only:
*
* - value_type must be moveable.
* - Pointer stability is not kept under rehashing.
* - No extract API.
*
* try_emplace, erase and find support heterogeneous lookup by default,
* that is, without checking for any ::is_transparent typedefs --the
* checking is done by boost::unordered_(flat|node)_(map|set).
*/
template <typename TypePolicy,typename Hash,typename Pred,typename Allocator>
using table_core_impl=
table_core<TypePolicy,group15<plain_integral>,table_arrays,
plain_size_control,Hash,Pred,Allocator>;
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
#if defined(BOOST_MSVC)
#pragma warning(push)
#pragma warning(disable:4714) /* marked as __forceinline not inlined */
#endif
template<typename TypePolicy,typename Hash,typename Pred,typename Allocator>
class table:table_core_impl<TypePolicy,Hash,Pred,Allocator>
{
using super=table_core_impl<TypePolicy,Hash,Pred,Allocator>;
using type_policy=typename super::type_policy;
using group_type=typename super::group_type;
using super::N;
using prober=typename super::prober;
using locator=typename super::locator;
public:
using key_type=typename super::key_type;
using init_type=typename super::init_type;
using value_type=typename super::value_type;
using element_type=typename super::element_type;
private:
static constexpr bool has_mutable_iterator=
!std::is_same<key_type,value_type>::value;
public:
using hasher=typename super::hasher;
using key_equal=typename super::key_equal;
using allocator_type=typename super::allocator_type;
using pointer=typename super::pointer;
using const_pointer=typename super::const_pointer;
using reference=typename super::reference;
using const_reference=typename super::const_reference;
using size_type=typename super::size_type;
using difference_type=typename super::difference_type;
using const_iterator=table_iterator<type_policy,group_type,true>;
using iterator=typename std::conditional<
has_mutable_iterator,
table_iterator<type_policy,group_type,false>,
const_iterator>::type;
table(
std::size_t n=default_bucket_count,const Hash& h_=Hash(),
const Pred& pred_=Pred(),const Allocator& al_=Allocator()):
super{n,h_,pred_,al_}
{}
table(const table& x)=default;
table(table&& x)=default;
table(const table& x,const Allocator& al_):super{x,al_}{}
table(table&& x,const Allocator& al_):super{std::move(x),al_}{}
~table()=default;
table& operator=(const table& x)=default;
table& operator=(table&& x)=default;
using super::get_allocator;
iterator begin()noexcept
{
iterator it{this->arrays.groups,0,this->arrays.elements};
if(this->arrays.elements&&
!(this->arrays.groups[0].match_occupied()&0x1))++it;
return it;
}
const_iterator begin()const noexcept
{return const_cast<table*>(this)->begin();}
iterator end()noexcept{return {};}
const_iterator end()const noexcept{return const_cast<table*>(this)->end();}
const_iterator cbegin()const noexcept{return begin();}
const_iterator cend()const noexcept{return end();}
using super::empty;
using super::size;
using super::max_size;
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args)
{
auto x=alloc_make_insert_type<type_policy>(
this->al(),std::forward<Args>(args)...);
return emplace_impl(type_policy::move(x.value()));
}
template<typename Key,typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> try_emplace(
Key&& x,Args&&... args)
{
return emplace_impl(
try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);
}
BOOST_FORCEINLINE std::pair<iterator,bool>
insert(const init_type& x){return emplace_impl(x);}
BOOST_FORCEINLINE std::pair<iterator,bool>
insert(init_type&& x){return emplace_impl(std::move(x));}
/* template<typename=void> tilts call ambiguities in favor of init_type */
template<typename=void>
BOOST_FORCEINLINE std::pair<iterator,bool>
insert(const value_type& x){return emplace_impl(x);}
template<typename=void>
BOOST_FORCEINLINE std::pair<iterator,bool>
insert(value_type&& x){return emplace_impl(std::move(x));}
template<typename T=element_type>
BOOST_FORCEINLINE
typename std::enable_if<
!std::is_same<T,value_type>::value,
std::pair<iterator,bool>
>::type
insert(element_type&& x){return emplace_impl(std::move(x));}
template<
bool dependent_value=false,
typename std::enable_if<
has_mutable_iterator||dependent_value>::type* =nullptr
>
void erase(iterator pos)noexcept{return erase(const_iterator(pos));}
BOOST_FORCEINLINE
void erase(const_iterator pos)noexcept
{
super::erase(pos.pc,pos.p);
}
template<typename Key>
BOOST_FORCEINLINE
auto erase(Key&& x) -> typename std::enable_if<
!std::is_convertible<Key,iterator>::value&&
!std::is_convertible<Key,const_iterator>::value, std::size_t>::type
{
auto it=find(x);
if(it!=end()){
erase(it);
return 1;
}
else return 0;
}
void swap(table& x)
noexcept(noexcept(std::declval<super&>().swap(std::declval<super&>())))
{
super::swap(x);
}
using super::clear;
element_type extract(const_iterator pos)
{
BOOST_ASSERT(pos!=end());
erase_on_exit e{*this,pos};
(void)e;
return std::move(*pos.p);
}
// TODO: should we accept different allocator too?
template<typename Hash2,typename Pred2>
void merge(table<TypePolicy,Hash2,Pred2,Allocator>& x)
{
x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){
erase_on_exit e{x,{pg,n,p}};
if(!emplace_impl(type_policy::move(*p)).second)e.rollback();
});
}
template<typename Hash2,typename Pred2>
void merge(table<TypePolicy,Hash2,Pred2,Allocator>&& x){merge(x);}
using super::hash_function;
using super::key_eq;
template<typename Key>
BOOST_FORCEINLINE iterator find(const Key& x)
{
return make_iterator(super::find(x));
}
template<typename Key>
BOOST_FORCEINLINE const_iterator find(const Key& x)const
{
return const_cast<table*>(this)->find(x);
}
using super::capacity;
using super::load_factor;
using super::max_load_factor;
using super::max_load;
using super::rehash;
using super::reserve;
template<typename Predicate>
friend std::size_t erase_if(table& x,Predicate& pr)
{
using value_reference=typename std::conditional<
std::is_same<key_type,value_type>::value,
const_reference,
reference
>::type;
std::size_t s=x.size();
x.for_all_elements(
[&](group_type* pg,unsigned int n,element_type* p){
if(pr(const_cast<value_reference>(type_policy::value_from(*p)))){
x.super::erase(pg,n,p);
}
});
return std::size_t(s-x.size());
}
friend bool operator==(const table& x,const table& y)
{
return static_cast<const super&>(x)==static_cast<const super&>(y);
}
friend bool operator!=(const table& x,const table& y){return !(x==y);}
private:
struct erase_on_exit
{
erase_on_exit(table& x_,const_iterator it_):x{x_},it{it_}{}
~erase_on_exit(){if(!rollback_)x.erase(it);}
void rollback(){rollback_=true;}
table& x;
const_iterator it;
bool rollback_=false;
};
static inline iterator make_iterator(const locator& l)noexcept
{
return {l.pg,l.n,l.p};
}
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
{
const auto &k=this->key_from(std::forward<Args>(args)...);
auto hash=this->hash_for(k);
auto pos0=this->position_for(hash);
auto loc=super::find(k,pos0,hash);
if(loc){
return {make_iterator(loc),false};
}
if(BOOST_LIKELY(this->size_ctrl.size<this->size_ctrl.ml)){
return {
make_iterator(
this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...)),
true
};
}
else{
return {
make_iterator(
this->unchecked_emplace_with_rehash(
hash,std::forward<Args>(args)...)),
true
};
}
}
};
#if defined(BOOST_MSVC)
#pragma warning(pop) /* C4714 */
#endif
#include <boost/unordered/detail/foa/restore_wshadow.hpp>
} /* namespace foa */
} /* namespace detail */
} /* namespace unordered */
} /* namespace boost */
#endif
@@ -0,0 +1,52 @@
/* Copyright 2023 Joaquin M Lopez Munoz.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
#ifndef BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP
#define BOOST_UNORDERED_DETAIL_FOA_TUPLE_ROTATE_RIGHT_HPP
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/integer_sequence.hpp>
#include <tuple>
#include <utility>
namespace boost{
namespace unordered{
namespace detail{
namespace foa{
template<typename Tuple>
using tuple_rotate_right_return_type=mp11::mp_rotate_right_c<
typename std::remove_cv<typename std::remove_reference<Tuple>::type>::type,
1
>;
template<std::size_t... Is,typename Tuple>
tuple_rotate_right_return_type<Tuple>
tuple_rotate_right_aux(mp11::index_sequence<Is...>,Tuple&& x)
{
return tuple_rotate_right_return_type<Tuple>{
std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward<Tuple>(x))...};
}
template<typename Tuple>
tuple_rotate_right_return_type<Tuple> tuple_rotate_right(Tuple&& x)
{
using RawTuple=typename std::remove_cv<
typename std::remove_reference<Tuple>::type>::type;
return tuple_rotate_right_aux(
mp11::make_index_sequence<std::tuple_size<RawTuple>::value>{},
std::forward<Tuple>(x));
}
} /* namespace foa */
} /* namespace detail */
} /* namespace unordered */
} /* namespace boost */
#endif
+86
View File
@@ -61,4 +61,90 @@ namespace boost {
}
}
// BOOST_UNORDERED_EMPLACE_LIMIT = The maximum number of parameters in
// emplace (not including things like hints). Don't set it to a lower value, as
// that might break something.
#if !defined BOOST_UNORDERED_EMPLACE_LIMIT
#define BOOST_UNORDERED_EMPLACE_LIMIT 10
#endif
////////////////////////////////////////////////////////////////////////////////
// Configuration
//
// Unless documented elsewhere these configuration macros should be considered
// an implementation detail, I'll try not to break them, but you never know.
// Use Sun C++ workarounds
// I'm not sure which versions of the compiler require these workarounds, so
// I'm just using them of everything older than the current test compilers
// (as of May 2017).
#if !defined(BOOST_UNORDERED_SUN_WORKAROUNDS1)
#if BOOST_COMP_SUNPRO && BOOST_COMP_SUNPRO < BOOST_VERSION_NUMBER(5, 20, 0)
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 1
#else
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 0
#endif
#endif
// BOOST_UNORDERED_TUPLE_ARGS
//
// Maximum number of std::tuple members to support, or 0 if std::tuple
// isn't avaiable. More are supported when full C++11 is used.
// Already defined, so do nothing
#if defined(BOOST_UNORDERED_TUPLE_ARGS)
// Assume if we have C++11 tuple it's properly variadic,
// and just use a max number of 10 arguments.
#elif !defined(BOOST_NO_CXX11_HDR_TUPLE)
#define BOOST_UNORDERED_TUPLE_ARGS 10
// Visual C++ has a decent enough tuple for piecewise construction,
// so use that if available, using _VARIADIC_MAX for the maximum
// number of parameters. Note that this comes after the check
// for a full C++11 tuple.
#elif defined(BOOST_MSVC)
#if !BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT
#define BOOST_UNORDERED_TUPLE_ARGS 0
#elif defined(_VARIADIC_MAX)
#define BOOST_UNORDERED_TUPLE_ARGS _VARIADIC_MAX
#else
#define BOOST_UNORDERED_TUPLE_ARGS 5
#endif
// Assume that we don't have std::tuple
#else
#define BOOST_UNORDERED_TUPLE_ARGS 0
#endif
#if BOOST_UNORDERED_TUPLE_ARGS
#include <tuple>
#endif
// BOOST_UNORDERED_CXX11_CONSTRUCTION
//
// Use C++11 construction, requires variadic arguments, good construct support
// in allocator_traits and piecewise construction of std::pair
// Otherwise allocators aren't used for construction/destruction
#if BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT && \
!defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && BOOST_UNORDERED_TUPLE_ARGS
#if BOOST_COMP_SUNPRO && BOOST_LIB_STD_GNU
// Sun C++ std::pair piecewise construction doesn't seem to be exception safe.
// (At least for Sun C++ 12.5 using libstdc++).
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
#elif BOOST_COMP_GNUC && BOOST_COMP_GNUC < BOOST_VERSION_NUMBER(4, 7, 0)
// Piecewise construction in GCC 4.6 doesn't work for uncopyable types.
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
#elif !defined(BOOST_NO_CXX11_ALLOCATOR)
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 1
#endif
#endif
#if !defined(BOOST_UNORDERED_CXX11_CONSTRUCTION)
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
#endif
#endif
@@ -60,92 +60,6 @@
#include <type_traits>
#endif
////////////////////////////////////////////////////////////////////////////////
// Configuration
//
// Unless documented elsewhere these configuration macros should be considered
// an implementation detail, I'll try not to break them, but you never know.
// Use Sun C++ workarounds
// I'm not sure which versions of the compiler require these workarounds, so
// I'm just using them of everything older than the current test compilers
// (as of May 2017).
#if !defined(BOOST_UNORDERED_SUN_WORKAROUNDS1)
#if BOOST_COMP_SUNPRO && BOOST_COMP_SUNPRO < BOOST_VERSION_NUMBER(5, 20, 0)
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 1
#else
#define BOOST_UNORDERED_SUN_WORKAROUNDS1 0
#endif
#endif
// BOOST_UNORDERED_EMPLACE_LIMIT = The maximum number of parameters in
// emplace (not including things like hints). Don't set it to a lower value, as
// that might break something.
#if !defined BOOST_UNORDERED_EMPLACE_LIMIT
#define BOOST_UNORDERED_EMPLACE_LIMIT 10
#endif
// BOOST_UNORDERED_TUPLE_ARGS
//
// Maximum number of std::tuple members to support, or 0 if std::tuple
// isn't avaiable. More are supported when full C++11 is used.
// Already defined, so do nothing
#if defined(BOOST_UNORDERED_TUPLE_ARGS)
// Assume if we have C++11 tuple it's properly variadic,
// and just use a max number of 10 arguments.
#elif !defined(BOOST_NO_CXX11_HDR_TUPLE)
#define BOOST_UNORDERED_TUPLE_ARGS 10
// Visual C++ has a decent enough tuple for piecewise construction,
// so use that if available, using _VARIADIC_MAX for the maximum
// number of parameters. Note that this comes after the check
// for a full C++11 tuple.
#elif defined(BOOST_MSVC)
#if !BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT
#define BOOST_UNORDERED_TUPLE_ARGS 0
#elif defined(_VARIADIC_MAX)
#define BOOST_UNORDERED_TUPLE_ARGS _VARIADIC_MAX
#else
#define BOOST_UNORDERED_TUPLE_ARGS 5
#endif
// Assume that we don't have std::tuple
#else
#define BOOST_UNORDERED_TUPLE_ARGS 0
#endif
#if BOOST_UNORDERED_TUPLE_ARGS
#include <tuple>
#endif
// BOOST_UNORDERED_CXX11_CONSTRUCTION
//
// Use C++11 construction, requires variadic arguments, good construct support
// in allocator_traits and piecewise construction of std::pair
// Otherwise allocators aren't used for construction/destruction
#if BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT && \
!defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && BOOST_UNORDERED_TUPLE_ARGS
#if BOOST_COMP_SUNPRO && BOOST_LIB_STD_GNU
// Sun C++ std::pair piecewise construction doesn't seem to be exception safe.
// (At least for Sun C++ 12.5 using libstdc++).
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
#elif BOOST_COMP_GNUC && BOOST_COMP_GNUC < BOOST_VERSION_NUMBER(4, 7, 0)
// Piecewise construction in GCC 4.6 doesn't work for uncopyable types.
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
#elif !defined(BOOST_NO_CXX11_ALLOCATOR)
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 1
#endif
#endif
#if !defined(BOOST_UNORDERED_CXX11_CONSTRUCTION)
#define BOOST_UNORDERED_CXX11_CONSTRUCTION 0
#endif
#if BOOST_UNORDERED_CXX11_CONSTRUCTION
#include <boost/mp11/list.hpp>
#include <boost/mp11/algorithm.hpp>
@@ -20,6 +20,9 @@
#include <boost/type_traits/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/remove_const.hpp>
#include <iterator>
#include <utility>
#endif
// BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
@@ -101,6 +104,16 @@ namespace boost {
!boost::is_integral<H>::value && !is_allocator_v<H>;
template <class P> constexpr bool const is_pred_v = !is_allocator_v<P>;
template <typename T>
using iter_key_t =
typename std::iterator_traits<T>::value_type::first_type;
template <typename T>
using iter_val_t =
typename std::iterator_traits<T>::value_type::second_type;
template <typename T>
using iter_to_alloc_t =
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
#endif
} // namespace detail
} // namespace unordered
+7 -26
View File
@@ -10,7 +10,7 @@
#pragma once
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/table.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_flat_map_fwd.hpp>
@@ -100,6 +100,11 @@ namespace boost {
table_type table_;
template <class K, class V, class H, class KE, class A>
bool friend operator==(
unordered_flat_map<K, V, H, KE, A> const& lhs,
unordered_flat_map<K, V, H, KE, A> const& rhs);
template <class K, class V, class H, class KE, class A, class Pred>
typename unordered_flat_map<K, V, H, KE, A>::size_type friend erase_if(
unordered_flat_map<K, V, H, KE, A>& set, Pred pred);
@@ -702,19 +707,7 @@ namespace boost {
unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
{
if (&lhs == &rhs) {
return true;
}
return (lhs.size() == rhs.size()) && ([&] {
for (auto const& kvp : lhs) {
auto pos = rhs.find(kvp.first);
if ((pos == rhs.end()) || (*pos != kvp)) {
return false;
}
}
return true;
})();
return lhs.table_ == rhs.table_;
}
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
@@ -748,18 +741,6 @@ namespace boost {
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
namespace detail {
template <typename T>
using iter_key_t =
typename std::iterator_traits<T>::value_type::first_type;
template <typename T>
using iter_val_t =
typename std::iterator_traits<T>::value_type::second_type;
template <typename T>
using iter_to_alloc_t =
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
} // namespace detail
template <class InputIterator,
class Hash =
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
+7 -14
View File
@@ -10,7 +10,7 @@
#pragma once
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/table.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_flat_set_fwd.hpp>
@@ -69,6 +69,11 @@ namespace boost {
table_type table_;
template <class K, class H, class KE, class A>
bool friend operator==(
unordered_flat_set<K, H, KE, A> const& lhs,
unordered_flat_set<K, H, KE, A> const& rhs);
template <class K, class H, class KE, class A, class Pred>
typename unordered_flat_set<K, H, KE, A>::size_type friend erase_if(
unordered_flat_set<K, H, KE, A>& set, Pred pred);
@@ -499,19 +504,7 @@ namespace boost {
unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,
unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs)
{
if (&lhs == &rhs) {
return true;
}
return (lhs.size() == rhs.size()) && ([&] {
for (auto const& key : lhs) {
auto pos = rhs.find(key);
if ((pos == rhs.end()) || (key != *pos)) {
return false;
}
}
return true;
})();
return lhs.table_ == rhs.table_;
}
template <class Key, class Hash, class KeyEqual, class Allocator>
-12
View File
@@ -1061,18 +1061,6 @@ namespace boost {
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
namespace detail {
template <typename T>
using iter_key_t =
typename std::iterator_traits<T>::value_type::first_type;
template <typename T>
using iter_val_t =
typename std::iterator_traits<T>::value_type::second_type;
template <typename T>
using iter_to_alloc_t =
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
} // namespace detail
template <class InputIterator,
class Hash =
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
+7 -26
View File
@@ -10,9 +10,9 @@
#pragma once
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/foa/table.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_map_fwd.hpp>
@@ -187,6 +187,11 @@ namespace boost {
table_type table_;
template <class K, class V, class H, class KE, class A>
bool friend operator==(
unordered_node_map<K, V, H, KE, A> const& lhs,
unordered_node_map<K, V, H, KE, A> const& rhs);
template <class K, class V, class H, class KE, class A, class Pred>
typename unordered_node_map<K, V, H, KE, A>::size_type friend erase_if(
unordered_node_map<K, V, H, KE, A>& set, Pred pred);
@@ -854,19 +859,7 @@ namespace boost {
unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
{
if (&lhs == &rhs) {
return true;
}
return (lhs.size() == rhs.size()) && ([&] {
for (auto const& kvp : lhs) {
auto pos = rhs.find(kvp.first);
if ((pos == rhs.end()) || (*pos != kvp)) {
return false;
}
}
return true;
})();
return lhs.table_ == rhs.table_;
}
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
@@ -900,18 +893,6 @@ namespace boost {
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
namespace detail {
template <typename T>
using iter_key_t =
typename std::iterator_traits<T>::value_type::first_type;
template <typename T>
using iter_val_t =
typename std::iterator_traits<T>::value_type::second_type;
template <typename T>
using iter_to_alloc_t =
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
} // namespace detail
template <class InputIterator,
class Hash =
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
+7 -14
View File
@@ -10,9 +10,9 @@
#pragma once
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/foa/table.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_set_fwd.hpp>
@@ -143,6 +143,11 @@ namespace boost {
table_type table_;
template <class K, class H, class KE, class A>
bool friend operator==(
unordered_node_set<K, H, KE, A> const& lhs,
unordered_node_set<K, H, KE, A> const& rhs);
template <class K, class H, class KE, class A, class Pred>
typename unordered_node_set<K, H, KE, A>::size_type friend erase_if(
unordered_node_set<K, H, KE, A>& set, Pred pred);
@@ -638,19 +643,7 @@ namespace boost {
unordered_node_set<Key, Hash, KeyEqual, Allocator> const& lhs,
unordered_node_set<Key, Hash, KeyEqual, Allocator> const& rhs)
{
if (&lhs == &rhs) {
return true;
}
return (lhs.size() == rhs.size()) && ([&] {
for (auto const& key : lhs) {
auto pos = rhs.find(key);
if ((pos == rhs.end()) || (key != *pos)) {
return false;
}
}
return true;
})();
return lhs.table_ == rhs.table_;
}
template <class Key, class Hash, class KeyEqual, class Allocator>
+153 -7
View File
@@ -6,12 +6,158 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST)
if(HAVE_BOOST_TEST)
boost_test_jamfile(
FILE Jamfile.v2
LINK_LIBRARIES
Boost::unordered
Boost::core
Boost::concept_check
)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
set(BOOST_TEST_LINK_LIBRARIES Boost::unordered Boost::core Boost::concept_check)
function(fca_tests)
boost_test(PREFIX boost_unordered ${ARGN})
endfunction()
function(foa_tests)
boost_test(PREFIX boost_unordered_foa COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS ${ARGN})
endfunction()
function(cfoa_tests)
boost_test(PREFIX boost_unordered_cfoa LINK_LIBRARIES Threads::Threads ${ARGN})
endfunction()
# FCA tests
fca_tests(SOURCES unordered/prime_fmod_tests.cpp)
fca_tests(SOURCES unordered/fwd_set_test.cpp)
fca_tests(SOURCES unordered/fwd_map_test.cpp)
fca_tests(SOURCES unordered/allocator_traits.cpp)
fca_tests(SOURCES unordered/minimal_allocator.cpp)
fca_tests(SOURCES unordered/compile_set.cpp)
fca_tests(SOURCES unordered/compile_map.cpp)
fca_tests(SOURCES unordered/noexcept_tests.cpp)
fca_tests(SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp)
fca_tests(SOURCES unordered/incomplete_test.cpp)
fca_tests(SOURCES unordered/simple_tests.cpp)
fca_tests(SOURCES unordered/equivalent_keys_tests.cpp)
fca_tests(SOURCES unordered/constructor_tests.cpp)
fca_tests(SOURCES unordered/copy_tests.cpp)
fca_tests(SOURCES unordered/move_tests.cpp)
fca_tests(SOURCES unordered/post_move_tests.cpp)
fca_tests(SOURCES unordered/assign_tests.cpp)
fca_tests(SOURCES unordered/insert_tests.cpp)
fca_tests(SOURCES unordered/insert_stable_tests.cpp)
fca_tests(SOURCES unordered/insert_hint_tests.cpp)
fca_tests(SOURCES unordered/emplace_tests.cpp)
fca_tests(SOURCES unordered/unnecessary_copy_tests.cpp)
fca_tests(SOURCES unordered/erase_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SUPPRESS_DEPRECATED)
fca_tests(SOURCES unordered/erase_equiv_tests.cpp)
fca_tests(SOURCES unordered/extract_tests.cpp)
fca_tests(SOURCES unordered/node_handle_tests.cpp)
fca_tests(SOURCES unordered/merge_tests.cpp)
fca_tests(SOURCES unordered/find_tests.cpp)
fca_tests(SOURCES unordered/at_tests.cpp)
fca_tests(SOURCES unordered/bucket_tests.cpp)
fca_tests(SOURCES unordered/load_factor_tests.cpp)
fca_tests(SOURCES unordered/rehash_tests.cpp)
fca_tests(SOURCES unordered/equality_tests.cpp)
fca_tests(SOURCES unordered/swap_tests.cpp)
fca_tests(SOURCES unordered/deduction_tests.cpp)
fca_tests(SOURCES unordered/scoped_allocator.cpp)
fca_tests(SOURCES unordered/transparent_tests.cpp)
fca_tests(SOURCES unordered/reserve_tests.cpp)
fca_tests(SOURCES unordered/contains_tests.cpp)
fca_tests(SOURCES unordered/erase_if.cpp)
fca_tests(SOURCES unordered/scary_tests.cpp)
fca_tests(SOURCES exception/constructor_exception_tests.cpp)
fca_tests(SOURCES exception/copy_exception_tests.cpp)
fca_tests(SOURCES exception/assign_exception_tests.cpp)
fca_tests(SOURCES exception/move_assign_exception_tests.cpp)
fca_tests(SOURCES exception/insert_exception_tests.cpp)
fca_tests(SOURCES exception/erase_exception_tests.cpp)
fca_tests(SOURCES exception/rehash_exception_tests.cpp)
fca_tests(SOURCES exception/swap_exception_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SWAP_METHOD=2)
fca_tests(SOURCES exception/merge_exception_tests.cpp)
fca_tests(SOURCES exception/less_tests.cpp)
fca_tests(SOURCES unordered/narrow_cast_tests.cpp)
fca_tests(SOURCES unordered/compile_set.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_set)
fca_tests(SOURCES unordered/compile_map.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_map)
fca_tests(SOURCES unordered/copy_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_copy)
fca_tests(SOURCES unordered/move_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_move)
fca_tests(SOURCES unordered/assign_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_assign)
fca_tests(SOURCES quick.cpp)
fca_tests(TYPE compile-fail NAME insert_node_type_fail_map COMPILE_DEFINITIONS UNORDERED_TEST_MAP SOURCES unordered/insert_node_type_fail.cpp)
fca_tests(TYPE compile-fail NAME insert_node_type_fail_multimap COMPILE_DEFINITIONS UNORDERED_TEST_MULTIMAP SOURCES unordered/insert_node_type_fail.cpp)
fca_tests(TYPE compile-fail NAME insert_node_type_fail_set COMPILE_DEFINITIONS UNORDERED_TEST_SET SOURCES unordered/insert_node_type_fail.cpp)
fca_tests(TYPE compile-fail NAME insert_node_type_fail_multiset COMPILE_DEFINITIONS UNORDERED_TEST_MULTISET SOURCES unordered/insert_node_type_fail.cpp)
# FOA tests
foa_tests(SOURCES unordered/fwd_set_test.cpp)
foa_tests(SOURCES unordered/fwd_map_test.cpp)
foa_tests(SOURCES unordered/compile_set.cpp)
foa_tests(SOURCES unordered/compile_map.cpp)
foa_tests(SOURCES unordered/noexcept_tests.cpp)
foa_tests(SOURCES unordered/incomplete_test.cpp)
foa_tests(SOURCES unordered/simple_tests.cpp)
foa_tests(SOURCES unordered/equivalent_keys_tests.cpp)
foa_tests(SOURCES unordered/constructor_tests.cpp)
foa_tests(SOURCES unordered/copy_tests.cpp)
foa_tests(SOURCES unordered/move_tests.cpp)
foa_tests(SOURCES unordered/post_move_tests.cpp)
foa_tests(SOURCES unordered/assign_tests.cpp)
foa_tests(SOURCES unordered/insert_tests.cpp)
foa_tests(SOURCES unordered/insert_hint_tests.cpp)
foa_tests(SOURCES unordered/emplace_tests.cpp)
foa_tests(SOURCES unordered/erase_tests.cpp)
foa_tests(SOURCES unordered/merge_tests.cpp)
foa_tests(SOURCES unordered/find_tests.cpp)
foa_tests(SOURCES unordered/at_tests.cpp)
foa_tests(SOURCES unordered/load_factor_tests.cpp)
foa_tests(SOURCES unordered/rehash_tests.cpp)
foa_tests(SOURCES unordered/equality_tests.cpp)
foa_tests(SOURCES unordered/swap_tests.cpp)
foa_tests(SOURCES unordered/transparent_tests.cpp)
foa_tests(SOURCES unordered/reserve_tests.cpp)
foa_tests(SOURCES unordered/contains_tests.cpp)
foa_tests(SOURCES unordered/erase_if.cpp)
foa_tests(SOURCES unordered/scary_tests.cpp)
foa_tests(SOURCES unordered/init_type_insert_tests.cpp)
foa_tests(SOURCES unordered/max_load_tests.cpp)
foa_tests(SOURCES unordered/extract_tests.cpp)
foa_tests(SOURCES unordered/node_handle_tests.cpp)
foa_tests(SOURCES unordered/uses_allocator.cpp)
foa_tests(SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp )
foa_tests(SOURCES unordered/scoped_allocator.cpp)
foa_tests(SOURCES unordered/hash_is_avalanching_test.cpp)
foa_tests(SOURCES exception/constructor_exception_tests.cpp)
foa_tests(SOURCES exception/copy_exception_tests.cpp)
foa_tests(SOURCES exception/assign_exception_tests.cpp)
foa_tests(SOURCES exception/move_assign_exception_tests.cpp)
foa_tests(SOURCES exception/insert_exception_tests.cpp)
foa_tests(SOURCES exception/erase_exception_tests.cpp)
foa_tests(SOURCES exception/rehash_exception_tests.cpp)
foa_tests(SOURCES exception/swap_exception_tests.cpp)
foa_tests(SOURCES exception/merge_exception_tests.cpp)
# CFOA tests
cfoa_tests(SOURCES cfoa/latch_tests.cpp)
cfoa_tests(SOURCES cfoa/insert_tests.cpp)
cfoa_tests(SOURCES cfoa/erase_tests.cpp)
cfoa_tests(SOURCES cfoa/try_emplace_tests.cpp)
cfoa_tests(SOURCES cfoa/emplace_tests.cpp)
cfoa_tests(SOURCES cfoa/visit_tests.cpp)
cfoa_tests(SOURCES cfoa/constructor_tests.cpp)
cfoa_tests(SOURCES cfoa/assign_tests.cpp)
cfoa_tests(SOURCES cfoa/clear_tests.cpp)
cfoa_tests(SOURCES cfoa/swap_tests.cpp)
cfoa_tests(SOURCES cfoa/merge_tests.cpp)
cfoa_tests(SOURCES cfoa/rehash_tests.cpp)
cfoa_tests(SOURCES cfoa/equality_tests.cpp)
cfoa_tests(SOURCES cfoa/fwd_tests.cpp)
cfoa_tests(SOURCES cfoa/exception_insert_tests.cpp)
cfoa_tests(SOURCES cfoa/exception_erase_tests.cpp)
cfoa_tests(SOURCES cfoa/exception_constructor_tests.cpp)
cfoa_tests(SOURCES cfoa/exception_assign_tests.cpp)
cfoa_tests(SOURCES cfoa/exception_merge_tests.cpp)
endif()
+87 -38
View File
@@ -104,50 +104,51 @@ import ../../config/checks/config : requires ;
CPP11 = [ requires cxx11_constexpr cxx11_noexcept cxx11_decltype cxx11_alignas ] ;
rule build_foa ( name )
local FOA_TESTS =
fwd_set_test
fwd_map_test
compile_set
compile_map
noexcept_tests
incomplete_test
simple_tests
equivalent_keys_tests
constructor_tests
copy_tests
move_tests
post_move_tests
assign_tests
insert_tests
insert_hint_tests
emplace_tests
erase_tests
merge_tests
find_tests
at_tests
load_factor_tests
rehash_tests
equality_tests
swap_tests
transparent_tests
reserve_tests
contains_tests
erase_if
scary_tests
init_type_insert_tests
max_load_tests
extract_tests
node_handle_tests
uses_allocator
;
for local test in $(FOA_TESTS)
{
run unordered/$(name).cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_$(name) ;
run unordered/$(test).cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_$(test) ;
}
build_foa fwd_set_test ;
build_foa fwd_map_test ;
build_foa compile_set ;
build_foa compile_map ;
build_foa noexcept_tests ;
run unordered/link_test_1.cpp unordered/link_test_2.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_link_test ;
build_foa incomplete_test ;
build_foa simple_tests ;
build_foa equivalent_keys_tests ;
build_foa constructor_tests ;
build_foa copy_tests ;
build_foa move_tests ;
build_foa post_move_tests ;
build_foa assign_tests ;
build_foa insert_tests ;
build_foa insert_hint_tests ;
build_foa emplace_tests ;
build_foa erase_tests ;
build_foa merge_tests ;
build_foa find_tests ;
build_foa at_tests ;
build_foa load_factor_tests ;
build_foa rehash_tests ;
build_foa equality_tests ;
build_foa swap_tests ;
run unordered/scoped_allocator.cpp : : : $(CPP11) <toolset>msvc-14.0:<build>no <define>BOOST_UNORDERED_FOA_TESTS : foa_scoped_allocator ;
build_foa transparent_tests ;
build_foa reserve_tests ;
build_foa contains_tests ;
build_foa erase_if ;
build_foa scary_tests ;
build_foa init_type_insert_tests ;
build_foa max_load_tests ;
build_foa extract_tests ;
build_foa node_handle_tests ;
build_foa uses_allocator ;
run unordered/hash_is_avalanching_test.cpp ;
run exception/constructor_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_constructor_exception_tests ;
run exception/copy_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_copy_exception_tests ;
run exception/assign_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_assign_exception_tests ;
@@ -157,3 +158,51 @@ run exception/erase_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORD
run exception/rehash_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_rehash_exception_tests ;
run exception/swap_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_swap_exception_tests ;
run exception/merge_exception_tests.cpp : : : $(CPP11) <define>BOOST_UNORDERED_FOA_TESTS : foa_merge_exception_tests ;
alias foa_tests :
foa_$(FOA_TESTS)
foa_link_test
foa_scoped_allocator
hash_is_avalanching_test
foa_constructor_exception_tests
foa_copy_exception_tests
foa_assign_exception_tests
foa_move_assign_exception_tests
foa_insert_exception_tests
foa_erase_exception_tests
foa_rehash_exception_tests
foa_swap_exception_tests
foa_merge_exception_tests
;
local CFOA_TESTS =
latch_tests
insert_tests
erase_tests
try_emplace_tests
emplace_tests
visit_tests
constructor_tests
assign_tests
clear_tests
swap_tests
merge_tests
rehash_tests
equality_tests
fwd_tests
exception_insert_tests
exception_erase_tests
exception_constructor_tests
exception_assign_tests
exception_merge_tests
;
for local test in $(CFOA_TESTS)
{
run cfoa/$(test).cpp
: requirements $(CPP11) <threading>multi
: target-name cfoa_$(test)
;
}
alias cfoa_tests : cfoa_$(CFOA_TESTS) ;
+827
View File
@@ -0,0 +1,827 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#if defined(__clang__) && defined(__has_warning)
#if __has_warning("-Wself-assign-overloaded")
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
#endif
#if __has_warning("-Wself-move")
#pragma clang diagnostic ignored "-Wself-move"
#endif
#endif /* defined(__clang__) && defined(__has_warning) */
#if defined(BOOST_GCC) && BOOST_GCC >= 130000
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wself-move"
#endif
test::seed_t initialize_seed{2762556623};
using test::default_generator;
using test::limited_range;
using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
using map_value_type = typename map_type::value_type;
template <class T> struct pocca_allocator
{
using propagate_on_container_copy_assignment = std::true_type;
int x_ = -1;
using value_type = T;
pocca_allocator() = default;
pocca_allocator(pocca_allocator const&) = default;
pocca_allocator(pocca_allocator&&) = default;
pocca_allocator(int const x) : x_{x} {}
pocca_allocator& operator=(pocca_allocator const& rhs)
{
if (this != &rhs) {
x_ = rhs.x_;
}
return *this;
}
template <class U> pocca_allocator(pocca_allocator<U> const& rhs) : x_{rhs.x_}
{
}
T* allocate(std::size_t n)
{
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
bool operator==(pocca_allocator const& rhs) const { return x_ == rhs.x_; }
bool operator!=(pocca_allocator const& rhs) const { return x_ != rhs.x_; }
};
template <class T> struct pocma_allocator
{
using propagate_on_container_move_assignment = std::true_type;
int x_ = -1;
using value_type = T;
pocma_allocator() = default;
pocma_allocator(pocma_allocator const&) = default;
pocma_allocator(pocma_allocator&&) = default;
pocma_allocator(int const x) : x_{x} {}
pocma_allocator& operator=(pocma_allocator const& rhs)
{
if (this != &rhs) {
x_ = rhs.x_;
}
return *this;
}
template <class U> pocma_allocator(pocma_allocator<U> const& rhs) : x_{rhs.x_}
{
}
T* allocate(std::size_t n)
{
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
bool operator==(pocma_allocator const& rhs) const { return x_ == rhs.x_; }
bool operator!=(pocma_allocator const& rhs) const { return x_ != rhs.x_; }
};
namespace {
template <class G> void copy_assign(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
// lhs empty, rhs empty
{
raii::reset_counts();
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
thread_runner(values, [&x](boost::span<map_value_type> s) {
(void)s;
map_type y;
BOOST_TEST(x.empty());
BOOST_TEST(y.empty());
y = x;
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
BOOST_TEST(x.get_allocator() != y.get_allocator());
});
BOOST_TEST_EQ(raii::destructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_constructor, 0u);
}
// lhs non-empty, rhs empty
{
raii::reset_counts();
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
auto const old_size = reference_map.size();
thread_runner(values, [&x, &values](boost::span<map_value_type> s) {
(void)s;
map_type y(values.begin(), values.end(), values.size());
BOOST_TEST(x.empty());
BOOST_TEST(!y.empty());
y = x;
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
BOOST_TEST(x.get_allocator() != y.get_allocator());
BOOST_TEST(y.empty());
});
BOOST_TEST_EQ(raii::destructor, num_threads * (2 * old_size));
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, num_threads * 2 * reference_map.size());
}
check_raii_counts();
// lhs empty, rhs non-empty
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_cc = +raii::copy_constructor;
thread_runner(
values, [&x, &reference_map](boost::span<map_value_type> s) {
(void)s;
map_type y;
BOOST_TEST(!x.empty());
BOOST_TEST(y.empty());
y = x;
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
BOOST_TEST(x.get_allocator() != y.get_allocator());
test_matches_reference(y, reference_map);
});
BOOST_TEST_EQ(raii::destructor, num_threads * 2 * x.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, old_cc + (num_threads * 2 * x.size()));
}
check_raii_counts();
// lhs non-empty, rhs non-empty
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_size = x.size();
auto const old_cc = +raii::copy_constructor;
thread_runner(values, [&x, &values](boost::span<map_value_type> s) {
(void)s;
map_type y(values.begin(), values.end(), values.size());
BOOST_TEST(!x.empty());
BOOST_TEST(!y.empty());
y = x;
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
BOOST_TEST(x.get_allocator() != y.get_allocator());
});
BOOST_TEST_EQ(raii::destructor, 2 * num_threads * 2 * old_size);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, old_cc + (2 * num_threads * 2 * x.size()));
}
check_raii_counts();
// self-assign
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_cc = +raii::copy_constructor;
thread_runner(
values, [&x, &reference_map](boost::span<map_value_type> s) {
(void)s;
BOOST_TEST(!x.empty());
x = x;
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type(3));
test_matches_reference(x, reference_map);
});
BOOST_TEST_EQ(raii::destructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
}
check_raii_counts();
// propagation
{
using pocca_allocator_type =
pocca_allocator<std::pair<const raii, raii> >;
using pocca_map_type = boost::unordered::concurrent_flat_map<raii, raii,
hasher, key_equal, pocca_allocator_type>;
raii::reset_counts();
pocca_map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), pocca_allocator_type(3));
auto const old_size = x.size();
auto const old_cc = +raii::copy_constructor;
thread_runner(values, [&x, &values](boost::span<map_value_type> s) {
(void)s;
pocca_map_type y(values.begin(), values.end(), values.size());
BOOST_TEST(!x.empty());
BOOST_TEST(!y.empty());
BOOST_TEST(x.get_allocator() != y.get_allocator());
y = x;
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
BOOST_TEST(x.get_allocator() == y.get_allocator());
});
BOOST_TEST_EQ(raii::destructor, 2 * num_threads * 2 * old_size);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, old_cc + (2 * num_threads * 2 * x.size()));
}
check_raii_counts();
}
template <class G> void move_assign(G gen, test::random_generator rg)
{
using pocma_allocator_type = pocma_allocator<std::pair<const raii, raii> >;
using pocma_map_type = boost::unordered::concurrent_flat_map<raii, raii,
hasher, key_equal, pocma_allocator_type>;
BOOST_STATIC_ASSERT(
std::is_nothrow_move_assignable<boost::unordered::concurrent_flat_map<int,
int, std::hash<int>, std::equal_to<int>,
std::allocator<std::pair<int const, int> > > >::value);
BOOST_STATIC_ASSERT(
std::is_nothrow_move_assignable<boost::unordered::concurrent_flat_map<int,
int, std::hash<int>, std::equal_to<int>,
pocma_allocator<std::pair<int const, int> > > >::value);
BOOST_STATIC_ASSERT(
!std::is_nothrow_move_assignable<boost::unordered::concurrent_flat_map<
int, int, std::hash<int>, std::equal_to<int>,
stateful_allocator<std::pair<int const, int> > > >::value);
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
// move assignment has more complex requirements than copying
// equal allocators:
// lhs empty, rhs non-empty
// lhs non-empty, rhs empty
// lhs non-empty, rhs non-empty
//
// unequal allocators:
// lhs non-empty, rhs non-empty
//
// pocma
// self move-assign
// lhs empty, rhs empty
{
raii::reset_counts();
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
std::atomic<unsigned> num_transfers{0};
thread_runner(
values, [&x, &num_transfers](boost::span<map_value_type> s) {
(void)s;
map_type y(0, hasher(2), key_equal(1), allocator_type(3));
BOOST_TEST(x.empty());
BOOST_TEST(y.empty());
BOOST_TEST(x.get_allocator() == y.get_allocator());
y = std::move(x);
if (y.hash_function() == hasher(1)) {
++num_transfers;
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.hash_function(), hasher(2));
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
}
BOOST_TEST_EQ(x.hash_function(), hasher(2));
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
BOOST_TEST(x.get_allocator() == y.get_allocator());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(raii::destructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_constructor, 0u);
}
// lhs non-empty, rhs empty
{
raii::reset_counts();
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
std::atomic<unsigned> num_transfers{0};
thread_runner(
values, [&x, &values, &num_transfers](boost::span<map_value_type> s) {
(void)s;
map_type y(values.begin(), values.end(), values.size(), hasher(2),
key_equal(1), allocator_type(3));
BOOST_TEST(x.empty());
BOOST_TEST(!y.empty());
BOOST_TEST(x.get_allocator() == y.get_allocator());
y = std::move(x);
if (y.hash_function() == hasher(1)) {
++num_transfers;
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.hash_function(), hasher(2));
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
}
BOOST_TEST_EQ(x.hash_function(), hasher(2));
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
BOOST_TEST(x.get_allocator() == y.get_allocator());
BOOST_TEST(y.empty());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(raii::destructor, num_threads * 2 * reference_map.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, num_threads * 2 * reference_map.size());
}
check_raii_counts();
// lhs empty, rhs non-empty
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
std::atomic<unsigned> num_transfers{0};
thread_runner(values,
[&x, &reference_map, &num_transfers](boost::span<map_value_type> s) {
(void)s;
map_type y(allocator_type(3));
BOOST_TEST(y.empty());
BOOST_TEST(x.get_allocator() == y.get_allocator());
y = std::move(x);
if (!y.empty()) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.hash_function(), hasher(1));
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.hash_function(), hasher());
BOOST_TEST_EQ(y.key_eq(), key_equal());
}
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == y.get_allocator());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(raii::destructor, 2 * reference_map.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
}
check_raii_counts();
// lhs non-empty, rhs non-empty
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_size = x.size();
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
std::atomic<unsigned> num_transfers{0};
thread_runner(values, [&x, &values, &num_transfers, &reference_map](
boost::span<map_value_type> s) {
(void)s;
map_type y(values.begin(), values.end(), values.size(), hasher(2),
key_equal(1), allocator_type(3));
BOOST_TEST(!y.empty());
BOOST_TEST(x.get_allocator() == y.get_allocator());
y = std::move(x);
if (y.hash_function() == hasher(1)) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.hash_function(), hasher(2));
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
}
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.hash_function(), hasher(2));
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
BOOST_TEST(x.get_allocator() == y.get_allocator());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(
raii::destructor, 2 * old_size + num_threads * 2 * old_size);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::copy_constructor,
old_cc + (num_threads * 2 * reference_map.size()));
}
check_raii_counts();
// lhs non-empty, rhs non-empty, unequal allocators, no propagation
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_size = x.size();
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
std::atomic<unsigned> num_transfers{0};
thread_runner(values, [&x, &values, &num_transfers, &reference_map](
boost::span<map_value_type> s) {
(void)s;
map_type y(values.begin(), values.end(), values.size(), hasher(2),
key_equal(1), allocator_type(13));
BOOST_TEST(
!boost::allocator_is_always_equal<allocator_type>::type::value);
BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment<
allocator_type>::type::value);
BOOST_TEST(!y.empty());
BOOST_TEST(x.get_allocator() != y.get_allocator());
y = std::move(x);
if (y.hash_function() == hasher(1)) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.hash_function(), hasher(2));
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
}
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.hash_function(), hasher(2));
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
BOOST_TEST(x.get_allocator() != y.get_allocator());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(
raii::destructor, 2 * 2 * old_size + num_threads * 2 * old_size);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::move_constructor, old_mc + 2 * old_size);
BOOST_TEST_EQ(raii::copy_constructor,
old_cc + (num_threads * 2 * reference_map.size()));
}
check_raii_counts();
// lhs non-empty, rhs non-empty, pocma
{
raii::reset_counts();
pocma_map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), pocma_allocator_type(3));
auto const old_size = x.size();
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
std::atomic<unsigned> num_transfers{0};
thread_runner(values, [&x, &values, &num_transfers, &reference_map](
boost::span<map_value_type> s) {
(void)s;
pocma_map_type y(values.begin(), values.end(), values.size(), hasher(2),
key_equal(1), pocma_allocator_type(13));
BOOST_TEST(!y.empty());
BOOST_TEST(x.get_allocator() != y.get_allocator());
y = std::move(x);
if (y.hash_function() == hasher(1)) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.hash_function(), hasher(2));
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
}
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.hash_function(), hasher(2));
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
BOOST_TEST(x.get_allocator() == y.get_allocator());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(
raii::destructor, 2 * old_size + num_threads * 2 * old_size);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::copy_constructor,
old_cc + (num_threads * 2 * reference_map.size()));
}
check_raii_counts();
// self-assign
{
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
thread_runner(
values, [&x, &reference_map](boost::span<map_value_type> s) {
(void)s;
x = std::move(x);
BOOST_TEST(!x.empty());
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type(3));
test_matches_reference(x, reference_map);
});
BOOST_TEST_EQ(raii::destructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
}
check_raii_counts();
}
UNORDERED_AUTO_TEST (initializer_list_assignment) {
std::initializer_list<map_value_type> values{
map_value_type{raii{0}, raii{0}},
map_value_type{raii{1}, raii{1}},
map_value_type{raii{2}, raii{2}},
map_value_type{raii{3}, raii{3}},
map_value_type{raii{4}, raii{4}},
map_value_type{raii{5}, raii{5}},
map_value_type{raii{6}, raii{6}},
map_value_type{raii{6}, raii{6}},
map_value_type{raii{7}, raii{7}},
map_value_type{raii{8}, raii{8}},
map_value_type{raii{9}, raii{9}},
map_value_type{raii{10}, raii{10}},
map_value_type{raii{9}, raii{9}},
map_value_type{raii{8}, raii{8}},
map_value_type{raii{7}, raii{7}},
map_value_type{raii{6}, raii{6}},
map_value_type{raii{5}, raii{5}},
map_value_type{raii{4}, raii{4}},
map_value_type{raii{3}, raii{3}},
map_value_type{raii{2}, raii{2}},
map_value_type{raii{1}, raii{1}},
map_value_type{raii{0}, raii{0}},
};
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto v = std::vector<map_value_type>(values.begin(), values.end());
{
raii::reset_counts();
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
thread_runner(v, [&x, &values](boost::span<map_value_type> s) {
(void)s;
x = values;
});
test_matches_reference(x, reference_map);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type(3));
BOOST_TEST_EQ(raii::copy_constructor, num_threads * 2 * x.size());
BOOST_TEST_EQ(raii::destructor, (num_threads - 1) * 2 * x.size());
BOOST_TEST_EQ(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
check_raii_counts();
}
template <class G> void insert_and_assign(G gen, test::random_generator rg)
{
std::thread t1, t2, t3;
boost::latch start_latch(2), end_latch(2);
auto v1 = make_random_values(1024 * 16, [&] { return gen(rg); });
auto v2 = v1;
shuffle_values(v2);
auto reference_map =
boost::unordered_flat_map<raii, raii>(v1.begin(), v1.end());
raii::reset_counts();
{
map_type map1(v1.size(), hasher(1), key_equal(2), allocator_type(3));
map_type map2(v2.size(), hasher(1), key_equal(2), allocator_type(3));
t1 = std::thread([&v1, &map1, &start_latch, &end_latch] {
start_latch.arrive_and_wait();
for (auto const& v : v1) {
map1.insert(v);
}
end_latch.arrive_and_wait();
});
t2 = std::thread([&v2, &map2, &end_latch, &start_latch] {
start_latch.arrive_and_wait();
for (auto const& v : v2) {
map2.insert(v);
}
end_latch.arrive_and_wait();
});
std::atomic<unsigned> num_assignments{0};
t3 = std::thread([&map1, &map2, &end_latch, &num_assignments] {
while (map1.empty() && map2.empty()) {
std::this_thread::sleep_for(std::chrono::microseconds(10));
}
do {
map1 = map2;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
map2 = map1;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
++num_assignments;
} while (!end_latch.try_wait());
});
t1.join();
t2.join();
t3.join();
BOOST_TEST_GT(num_assignments, 0u);
test_fuzzy_matches_reference(map1, reference_map, rg);
test_fuzzy_matches_reference(map2, reference_map, rg);
}
check_raii_counts();
}
} // namespace
// clang-format off
UNORDERED_TEST(
copy_assign,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
move_assign,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
insert_and_assign,
((init_type_generator))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+125
View File
@@ -0,0 +1,125 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
test::seed_t initialize_seed{674140082};
using test::default_generator;
using test::limited_range;
using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
using map_value_type = typename map_type::value_type;
namespace {
template <class G> void clear_tests(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
BOOST_TEST_EQ(raii::destructor, 0u);
thread_runner(values, [&x](boost::span<map_value_type> s) {
(void)s;
x.clear();
});
BOOST_TEST(x.empty());
check_raii_counts();
}
template <class G> void insert_and_clear(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
std::thread t1, t2;
{
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
std::mutex m;
std::condition_variable cv;
std::atomic<bool> done{false};
std::atomic<unsigned> num_clears{0};
bool ready = false;
t1 = std::thread([&x, &values, &cv, &done, &m, &ready] {
for (auto i = 0u; i < values.size(); ++i) {
x.insert(values[i]);
if (i % (values.size() / 128) == 0) {
{
std::unique_lock<std::mutex> lk(m);
ready = true;
}
cv.notify_all();
}
}
done = true;
{
std::unique_lock<std::mutex> lk(m);
ready = true;
}
cv.notify_all();
});
t2 = std::thread([&x, &m, &cv, &done, &ready, &num_clears] {
do {
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&ready] { return ready; });
ready = false;
}
x.clear();
++num_clears;
} while (!done);
});
t1.join();
t2.join();
BOOST_TEST_GE(num_clears, 1u);
if (!x.empty()) {
test_fuzzy_matches_reference(x, reference_map, rg);
}
}
check_raii_counts();
}
} // namespace
// clang-format off
UNORDERED_TEST(
clear_tests,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(insert_and_clear,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+814
View File
@@ -0,0 +1,814 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
test::seed_t initialize_seed(4122023);
using test::default_generator;
using test::limited_range;
using test::sequential;
template <class T> struct soccc_allocator
{
int x_ = -1;
using value_type = T;
soccc_allocator() = default;
soccc_allocator(soccc_allocator const&) = default;
soccc_allocator(soccc_allocator&&) = default;
soccc_allocator(int const x) : x_{x} {}
template <class U> soccc_allocator(soccc_allocator<U> const& rhs) : x_{rhs.x_}
{
}
T* allocate(std::size_t n)
{
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
soccc_allocator select_on_container_copy_construction() const
{
return {x_ + 1};
}
bool operator==(soccc_allocator const& rhs) const { return x_ == rhs.x_; }
bool operator!=(soccc_allocator const& rhs) const { return x_ != rhs.x_; }
};
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
using map_value_type = typename map_type::value_type;
UNORDERED_AUTO_TEST (default_constructor) {
boost::unordered::concurrent_flat_map<raii, raii> x;
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.size(), 0u);
}
UNORDERED_AUTO_TEST (bucket_count_with_hasher_key_equal_and_allocator) {
raii::reset_counts();
{
map_type x(0);
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
}
{
map_type x(0, hasher(1));
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal());
}
{
map_type x(0, hasher(1), key_equal(2));
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
}
{
map_type x(0, hasher(1), key_equal(2), allocator_type{});
BOOST_TEST(x.empty());
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type{});
}
}
UNORDERED_AUTO_TEST (soccc) {
raii::reset_counts();
boost::unordered::concurrent_flat_map<raii, raii, hasher, key_equal,
soccc_allocator<std::pair<raii const, raii> > >
x;
boost::unordered::concurrent_flat_map<raii, raii, hasher, key_equal,
soccc_allocator<std::pair<raii const, raii> > >
y(x);
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
BOOST_TEST(y.get_allocator() != x.get_allocator());
}
namespace {
template <class G> void from_iterator_range(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
map_type x(values.begin(), values.end());
test_matches_reference(x, reference_map);
BOOST_TEST_GT(x.size(), 0u);
BOOST_TEST_LE(x.size(), values.size());
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type{});
if (rg == sequential) {
BOOST_TEST_EQ(x.size(), values.size());
}
}
{
map_type x(values.begin(), values.end(), 0);
test_matches_reference(x, reference_map);
BOOST_TEST_GT(x.size(), 0u);
BOOST_TEST_LE(x.size(), values.size());
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type{});
if (rg == sequential) {
BOOST_TEST_EQ(x.size(), values.size());
}
}
{
map_type x(values.begin(), values.end(), 0, hasher(1));
test_matches_reference(x, reference_map);
BOOST_TEST_GT(x.size(), 0u);
BOOST_TEST_LE(x.size(), values.size());
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type{});
if (rg == sequential) {
BOOST_TEST_EQ(x.size(), values.size());
}
}
{
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2));
test_matches_reference(x, reference_map);
BOOST_TEST_GT(x.size(), 0u);
BOOST_TEST_LE(x.size(), values.size());
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type{});
if (rg == sequential) {
BOOST_TEST_EQ(x.size(), values.size());
}
}
{
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type{});
test_matches_reference(x, reference_map);
BOOST_TEST_GT(x.size(), 0u);
BOOST_TEST_LE(x.size(), values.size());
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type{});
if (rg == sequential) {
BOOST_TEST_EQ(x.size(), values.size());
}
}
check_raii_counts();
}
template <class G> void copy_constructor(G gen, test::random_generator rg)
{
{
map_type x(0, hasher(1), key_equal(2), allocator_type{});
map_type y(x);
BOOST_TEST_EQ(y.size(), x.size());
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
BOOST_TEST(y.get_allocator() == x.get_allocator());
}
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type{});
thread_runner(
values, [&x, &reference_map](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
map_type y(x);
test_matches_reference(x, reference_map);
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.size(), x.size());
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
BOOST_TEST(y.get_allocator() == x.get_allocator());
});
}
check_raii_counts();
raii::reset_counts();
{
allocator_type a;
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), a);
thread_runner(
values, [&x, &reference_map, a](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
map_type y(x, a);
test_matches_reference(x, reference_map);
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.size(), x.size());
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
BOOST_TEST(y.get_allocator() == x.get_allocator());
});
}
check_raii_counts();
}
template <class G>
void copy_constructor_with_insertion(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
std::mutex m;
std::condition_variable cv;
bool ready = false;
{
map_type x(0, hasher(1), key_equal(2), allocator_type{});
auto f = [&x, &values, &m, &cv, &ready] {
{
std::lock_guard<std::mutex> guard(m);
ready = true;
}
cv.notify_all();
for (auto const& val : values) {
x.insert(val);
}
};
std::thread t1(f);
std::thread t2(f);
thread_runner(
values, [&x, &reference_map, &values, rg, &m, &cv, &ready](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return ready; });
}
map_type y(x);
BOOST_TEST_LE(y.size(), values.size());
BOOST_TEST_EQ(y.hash_function(), x.hash_function());
BOOST_TEST_EQ(y.key_eq(), x.key_eq());
BOOST_TEST(y.get_allocator() == x.get_allocator());
x.visit_all([&reference_map, rg](
typename map_type::value_type const& val) {
BOOST_TEST(reference_map.contains(val.first));
if (rg == sequential) {
BOOST_TEST_EQ(val.second, reference_map.find(val.first)->second);
}
});
});
t1.join();
t2.join();
}
check_raii_counts();
}
template <class G> void move_constructor(G gen, test::random_generator rg)
{
{
map_type x(0, hasher(1), key_equal(2), allocator_type{});
auto const old_size = x.size();
map_type y(std::move(x));
BOOST_TEST_EQ(y.size(), old_size);
BOOST_TEST_EQ(y.hash_function(), hasher(1));
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(y.get_allocator() == x.get_allocator());
}
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type{});
std::atomic_uint num_transfers{0};
auto const old_mc = +raii::move_constructor;
thread_runner(
values, [&x, &reference_map, &num_transfers](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
auto const old_size = x.size();
map_type y(std::move(x));
if (!y.empty()) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.size(), old_size);
BOOST_TEST_EQ(y.hash_function(), hasher(1));
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.size(), 0u);
BOOST_TEST_EQ(y.hash_function(), hasher());
BOOST_TEST_EQ(y.key_eq(), key_equal());
}
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(y.get_allocator() == x.get_allocator());
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
}
check_raii_counts();
// allocator-aware move constructor, unequal allocators
raii::reset_counts();
{
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type{1});
std::atomic_uint num_transfers{0};
auto const old_mc = +raii::move_constructor;
auto const old_size = x.size();
thread_runner(
values, [&x, &reference_map, &num_transfers, old_size](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
auto a = allocator_type{2};
BOOST_TEST(a != x.get_allocator());
map_type y(std::move(x), a);
if (!y.empty()) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.size(), old_size);
BOOST_TEST_EQ(y.hash_function(), hasher(1));
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.size(), 0u);
BOOST_TEST_EQ(y.hash_function(), hasher());
BOOST_TEST_EQ(y.key_eq(), key_equal());
}
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(y.get_allocator() != x.get_allocator());
BOOST_TEST(y.get_allocator() == a);
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(raii::move_constructor, old_mc + (2 * old_size));
}
check_raii_counts();
// allocator-aware move constructor, equal allocators
raii::reset_counts();
{
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type{1});
std::atomic_uint num_transfers{0};
auto const old_mc = +raii::move_constructor;
auto const old_size = x.size();
thread_runner(
values, [&x, &reference_map, &num_transfers, old_size](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
auto a = allocator_type{1};
BOOST_TEST(a == x.get_allocator());
map_type y(std::move(x), a);
if (!y.empty()) {
++num_transfers;
test_matches_reference(y, reference_map);
BOOST_TEST_EQ(y.size(), old_size);
BOOST_TEST_EQ(y.hash_function(), hasher(1));
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
} else {
BOOST_TEST_EQ(y.size(), 0u);
BOOST_TEST_EQ(y.hash_function(), hasher());
BOOST_TEST_EQ(y.key_eq(), key_equal());
}
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(y.get_allocator() == x.get_allocator());
BOOST_TEST(y.get_allocator() == a);
});
BOOST_TEST_EQ(num_transfers, 1u);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
}
check_raii_counts();
}
template <class G>
void move_constructor_with_insertion(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
std::mutex m;
std::condition_variable cv;
bool ready = false;
{
map_type x(0, hasher(1), key_equal(2), allocator_type{});
std::atomic_uint num_transfers{0};
std::thread t1([&x, &values] {
for (auto const& val : values) {
x.insert(val);
}
});
std::thread t2([&x, &m, &cv, &ready] {
while (x.empty()) {
std::this_thread::yield();
}
{
std::lock_guard<std::mutex> guard(m);
ready = true;
}
cv.notify_all();
});
thread_runner(
values, [&x, &reference_map, &num_transfers, rg, &m, &ready, &cv](
boost::span<span_value_type<decltype(values)> > s) {
(void)s;
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return ready; });
}
map_type y(std::move(x));
if (!y.empty()) {
++num_transfers;
y.cvisit_all([&reference_map, rg](map_value_type const& val) {
BOOST_TEST(reference_map.contains(val.first));
if (rg == sequential) {
BOOST_TEST_EQ(
val.second, reference_map.find(val.first)->second);
}
});
}
});
t1.join();
t2.join();
BOOST_TEST_GE(num_transfers, 1u);
}
check_raii_counts();
}
template <class G>
void iterator_range_with_allocator(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
allocator_type a;
map_type x(values.begin(), values.end(), a);
BOOST_TEST_GT(x.size(), 0u);
BOOST_TEST_LE(x.size(), values.size());
if (rg == sequential) {
BOOST_TEST_EQ(x.size(), values.size());
}
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == a);
test_fuzzy_matches_reference(x, reference_map, rg);
}
check_raii_counts();
}
UNORDERED_AUTO_TEST (explicit_allocator) {
raii::reset_counts();
{
allocator_type a;
map_type x(a);
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == a);
}
}
UNORDERED_AUTO_TEST (initializer_list_with_all_params) {
std::initializer_list<map_value_type> ilist{
map_value_type{raii{0}, raii{0}},
map_value_type{raii{1}, raii{1}},
map_value_type{raii{2}, raii{2}},
map_value_type{raii{3}, raii{3}},
map_value_type{raii{4}, raii{4}},
map_value_type{raii{5}, raii{5}},
map_value_type{raii{6}, raii{6}},
map_value_type{raii{6}, raii{6}},
map_value_type{raii{7}, raii{7}},
map_value_type{raii{8}, raii{8}},
map_value_type{raii{9}, raii{9}},
map_value_type{raii{10}, raii{10}},
map_value_type{raii{9}, raii{9}},
map_value_type{raii{8}, raii{8}},
map_value_type{raii{7}, raii{7}},
map_value_type{raii{6}, raii{6}},
map_value_type{raii{5}, raii{5}},
map_value_type{raii{4}, raii{4}},
map_value_type{raii{3}, raii{3}},
map_value_type{raii{2}, raii{2}},
map_value_type{raii{1}, raii{1}},
map_value_type{raii{0}, raii{0}},
};
{
raii::reset_counts();
map_type x(ilist, 0, hasher(1), key_equal(2), allocator_type(3));
BOOST_TEST_EQ(x.size(), 11u);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST(x.get_allocator() == allocator_type(3));
BOOST_TEST_EQ(raii::move_constructor, 0u);
}
check_raii_counts();
{
raii::reset_counts();
map_type x(ilist, allocator_type(3));
BOOST_TEST_EQ(x.size(), 11u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type(3));
BOOST_TEST_EQ(raii::move_constructor, 0u);
}
check_raii_counts();
{
raii::reset_counts();
map_type x(ilist, 0, allocator_type(3));
BOOST_TEST_EQ(x.size(), 11u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type(3));
BOOST_TEST_EQ(raii::move_constructor, 0u);
}
check_raii_counts();
{
raii::reset_counts();
map_type x(ilist, 0, hasher(1), allocator_type(3));
BOOST_TEST_EQ(x.size(), 11u);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type(3));
BOOST_TEST_EQ(raii::move_constructor, 0u);
}
check_raii_counts();
}
UNORDERED_AUTO_TEST (bucket_count_and_allocator) {
raii::reset_counts();
{
map_type x(0, allocator_type(3));
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type(3));
}
{
map_type x(4096, allocator_type(3));
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type(3));
}
}
UNORDERED_AUTO_TEST (bucket_count_with_hasher_and_allocator) {
raii::reset_counts();
{
map_type x(0, hasher(1), allocator_type(3));
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST_EQ(x.hash_function(), hasher(1));
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == allocator_type(3));
}
}
template <class G>
void iterator_range_with_bucket_count_and_allocator(
G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
allocator_type a(3);
map_type x(values.begin(), values.end(), 0, a);
test_fuzzy_matches_reference(x, reference_map, rg);
BOOST_TEST_EQ(x.hash_function(), hasher());
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == a);
}
check_raii_counts();
}
template <class G>
void iterator_range_with_bucket_count_hasher_and_allocator(
G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
allocator_type a(3);
hasher hf(1);
map_type x(values.begin(), values.end(), 0, hf, a);
test_fuzzy_matches_reference(x, reference_map, rg);
BOOST_TEST_EQ(x.hash_function(), hf);
BOOST_TEST_EQ(x.key_eq(), key_equal());
BOOST_TEST(x.get_allocator() == a);
}
check_raii_counts();
}
} // namespace
// clang-format off
UNORDERED_TEST(
from_iterator_range,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
copy_constructor,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
copy_constructor_with_insertion,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
move_constructor,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
move_constructor_with_insertion,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
iterator_range_with_allocator,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
iterator_range_with_bucket_count_and_allocator,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
iterator_range_with_bucket_count_hasher_and_allocator,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+167
View File
@@ -0,0 +1,167 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/core/ignore_unused.hpp>
namespace {
test::seed_t initialize_seed(335740237);
struct lvalue_emplacer_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto const& r : s) {
bool b = x.emplace(r.first.x_, r.second.x_);
if (b) {
++num_inserts;
}
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} lvalue_emplacer;
struct norehash_lvalue_emplacer_type : public lvalue_emplacer_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
x.reserve(values.size());
lvalue_emplacer_type::operator()(values, x);
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
}
} norehash_lvalue_emplacer;
struct lvalue_emplace_or_cvisit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
for (auto& r : s) {
bool b = x.emplace_or_cvisit(
r.first.x_, r.second.x_,
[&num_invokes](typename X::value_type const& v) {
(void)v;
++num_invokes;
});
if (b) {
++num_inserts;
}
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
}
} lvalue_emplace_or_cvisit;
struct lvalue_emplace_or_visit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
for (auto& r : s) {
bool b = x.emplace_or_visit(
r.first.x_, r.second.x_,
[&num_invokes](typename X::value_type& v) {
(void)v;
++num_invokes;
});
if (b) {
++num_inserts;
}
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
}
} lvalue_emplace_or_visit;
template <class X, class G, class F>
void emplace(X*, G gen, F emplacer, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
X x;
emplacer(values, x);
BOOST_TEST_EQ(x.size(), reference_map.size());
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
if (rg == test::sequential) {
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
}
}));
}
BOOST_TEST_GE(raii::default_constructor, 0u);
BOOST_TEST_GE(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 0u);
BOOST_TEST_GT(raii::destructor, 0u);
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
raii::move_constructor,
raii::destructor);
}
boost::unordered::concurrent_flat_map<raii, raii>* map;
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
emplace,
((map))
((value_type_generator)(init_type_generator))
((lvalue_emplacer)(norehash_lvalue_emplacer)
(lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+142
View File
@@ -0,0 +1,142 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
test::seed_t initialize_seed{1634048962};
using test::default_generator;
using test::limited_range;
using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
using map_value_type = typename map_type::value_type;
namespace {
UNORDERED_AUTO_TEST (simple_equality) {
{
map_type x1(
{{1, 11}, {2, 22}}, 0, hasher(1), key_equal(2), allocator_type(3));
map_type x2(
{{1, 11}, {2, 22}}, 0, hasher(2), key_equal(2), allocator_type(3));
map_type x3(
{{1, 11}, {2, 23}}, 0, hasher(2), key_equal(2), allocator_type(3));
map_type x4({{1, 11}}, 0, hasher(2), key_equal(2), allocator_type(3));
BOOST_TEST_EQ(x1.size(), x2.size());
BOOST_TEST(x1 == x2);
BOOST_TEST(!(x1 != x2));
BOOST_TEST_EQ(x1.size(), x3.size());
BOOST_TEST(!(x1 == x3));
BOOST_TEST(x1 != x3);
BOOST_TEST(x1.size() != x4.size());
BOOST_TEST(!(x1 == x4));
BOOST_TEST(x1 != x4);
}
}
template <class G> void insert_and_compare(G gen, test::random_generator rg)
{
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
boost::unordered_flat_map<raii, raii> reference_map(
vals1.begin(), vals1.end());
{
raii::reset_counts();
map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
map_type x2(vals1.begin(), vals1.end(), vals1.size(), hasher(2),
key_equal(2), allocator_type(3));
std::thread t1, t2;
std::mutex m;
std::condition_variable cv;
std::atomic_bool done{false};
std::atomic<unsigned> num_compares{0};
bool ready = false;
BOOST_TEST(x1.empty());
t1 = std::thread([&x1, &m, &cv, &vals1, &done, &ready] {
for (std::size_t idx = 0; idx < vals1.size(); ++idx) {
auto const& v = vals1[idx];
x1.insert(v);
if (idx % (vals1.size() / 128) == 0) {
{
std::unique_lock<std::mutex> lk(m);
ready = true;
}
cv.notify_all();
}
std::this_thread::yield();
}
done = true;
{
std::unique_lock<std::mutex> lk(m);
ready = true;
}
cv.notify_all();
});
t2 = std::thread([&x1, &x2, &m, &cv, &done, &num_compares, &ready] {
do {
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&ready] { return ready; });
ready = false;
}
volatile bool b = false;
b = x1 == x2;
b = x1 != x2;
b;
++num_compares;
std::this_thread::yield();
} while (!done);
BOOST_TEST(done);
});
t1.join();
t2.join();
BOOST_TEST_GE(num_compares, 1u);
BOOST_TEST(x1 == x2);
BOOST_TEST(!(x1 != x2));
test_matches_reference(x1, reference_map);
}
check_raii_counts();
}
} // namespace
// clang-format off
UNORDERED_TEST(
insert_and_compare,
((value_type_generator))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+408
View File
@@ -0,0 +1,408 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/core/ignore_unused.hpp>
namespace {
test::seed_t initialize_seed(3292023);
struct lvalue_eraser_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
raii::move_constructor,
raii::destructor + 2 * x.size());
thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
for (auto const& k : values) {
auto count = x.erase(k.first);
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
}
});
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size);
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST(x.empty());
BOOST_TEST_EQ(num_erased, old_size);
}
} lvalue_eraser;
struct transp_lvalue_eraser_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
raii::move_constructor,
raii::destructor + 2 * x.size());
thread_runner(values, [&num_erased, &x](boost::span<T> s) {
for (auto const& k : s) {
auto count = x.erase(k.first.x_);
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
}
});
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST(x.empty());
BOOST_TEST_EQ(num_erased, old_size);
}
} transp_lvalue_eraser;
struct lvalue_eraser_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
for (auto const& k : s) {
auto count = x.erase_if(k.first,
[threshold](value_type& v) { return v.second.x_ > threshold; });
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
}
});
BOOST_TEST_EQ(num_erased, expected_erasures);
BOOST_TEST_EQ(x.size(), old_size - num_erased);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
}
} lvalue_eraser_if;
struct transp_lvalue_eraser_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
for (auto const& k : s) {
auto count = x.erase_if(k.first.x_,
[threshold](value_type& v) { return v.second.x_ > threshold; });
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
}
});
BOOST_TEST_EQ(num_erased, expected_erasures);
BOOST_TEST_EQ(x.size(), old_size - num_erased);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
}
} transp_lvalue_eraser_if;
struct erase_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
thread_runner(
values, [&num_erased, &x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 128; ++i) {
auto count = x.erase_if(
[threshold](value_type& v) { return v.second.x_ > threshold; });
num_erased += count;
}
});
BOOST_TEST_EQ(num_erased, expected_erasures);
BOOST_TEST_EQ(x.size(), old_size - num_erased);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
}
} erase_if;
struct free_fn_erase_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
thread_runner(
values, [&num_erased, &x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 128; ++i) {
auto count = boost::unordered::erase_if(x,
[threshold](value_type& v) { return v.second.x_ > threshold; });
num_erased += count;
}
});
BOOST_TEST_EQ(num_erased, expected_erasures);
BOOST_TEST_EQ(x.size(), old_size - num_erased);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
}
} free_fn_erase_if;
struct erase_if_exec_policy_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
using value_type = typename X::value_type;
std::atomic<std::uint64_t> num_invokes{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
thread_runner(values, [&num_invokes, &x, threshold](boost::span<T> s) {
(void)s;
x.erase_if(
std::execution::par, [&num_invokes, threshold](value_type& v) {
++num_invokes;
return v.second.x_ > threshold;
});
});
BOOST_TEST_GE(+num_invokes, old_size);
BOOST_TEST_LE(+num_invokes, old_size * num_threads);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * expected_erasures);
#else
(void)values;
(void)x;
#endif
}
} erase_if_exec_policy;
template <class X, class G, class F>
void erase(X*, G gen, F eraser, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
X x;
x.insert(values.begin(), values.end());
BOOST_TEST_EQ(x.size(), reference_map.size());
test_fuzzy_matches_reference(x, reference_map, rg);
eraser(values, x);
test_fuzzy_matches_reference(x, reference_map, rg);
}
check_raii_counts();
}
boost::unordered::concurrent_flat_map<raii, raii>* map;
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
transp_key_equal>* transparent_map;
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
erase,
((map))
((value_type_generator)(init_type_generator))
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if)(erase_if_exec_policy))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
erase,
((transparent_map))
((value_type_generator)(init_type_generator))
((transp_lvalue_eraser)(transp_lvalue_eraser_if)(erase_if_exec_policy))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+172
View File
@@ -0,0 +1,172 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
namespace {
test::seed_t initialize_seed(1794114520);
template <class G> void copy_assign(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
raii::reset_counts();
unsigned num_throws = 0;
auto begin = values.begin();
auto mid =
values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
auto end = values.end();
auto reference_map = boost::unordered_flat_map<raii, raii>(begin, mid);
map_type x(
begin, mid, values.size(), hasher(1), key_equal(2), allocator_type(3));
map_type y(
mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4));
BOOST_TEST(!y.empty());
enable_exceptions();
for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) {
try {
y = x;
} catch (...) {
++num_throws;
}
}
disable_exceptions();
BOOST_TEST_GT(num_throws, 0u);
test_fuzzy_matches_reference(y, reference_map, rg);
}
check_raii_counts();
}
template <class G> void move_assign(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
raii::reset_counts();
unsigned num_throws = 0;
auto begin = values.begin();
auto mid =
values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
auto end = values.end();
auto reference_map = boost::unordered_flat_map<raii, raii>(begin, mid);
BOOST_TEST(
!boost::allocator_is_always_equal<allocator_type>::type::value);
BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment<
allocator_type>::type::value);
for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) {
disable_exceptions();
map_type x(begin, mid, values.size(), hasher(1), key_equal(2),
allocator_type(3));
map_type y(
mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4));
enable_exceptions();
try {
y = std::move(x);
} catch (...) {
++num_throws;
}
disable_exceptions();
test_fuzzy_matches_reference(y, reference_map, rg);
}
BOOST_TEST_GT(num_throws, 0u);
}
check_raii_counts();
}
UNORDERED_AUTO_TEST (intializer_list_assign) {
using value_type = typename map_type::value_type;
std::initializer_list<value_type> values{
value_type{raii{0}, raii{0}},
value_type{raii{1}, raii{1}},
value_type{raii{2}, raii{2}},
value_type{raii{3}, raii{3}},
value_type{raii{4}, raii{4}},
value_type{raii{5}, raii{5}},
value_type{raii{6}, raii{6}},
value_type{raii{6}, raii{6}},
value_type{raii{7}, raii{7}},
value_type{raii{8}, raii{8}},
value_type{raii{9}, raii{9}},
value_type{raii{10}, raii{10}},
value_type{raii{9}, raii{9}},
value_type{raii{8}, raii{8}},
value_type{raii{7}, raii{7}},
value_type{raii{6}, raii{6}},
value_type{raii{5}, raii{5}},
value_type{raii{4}, raii{4}},
value_type{raii{3}, raii{3}},
value_type{raii{2}, raii{2}},
value_type{raii{1}, raii{1}},
value_type{raii{0}, raii{0}},
};
{
raii::reset_counts();
unsigned num_throws = 0;
for (std::size_t i = 0; i < throw_threshold; ++i) {
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
enable_exceptions();
try {
x = values;
} catch (...) {
++num_throws;
}
disable_exceptions();
}
BOOST_TEST_GT(num_throws, 0u);
check_raii_counts();
}
}
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
copy_assign,
((exception_value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
move_assign,
((exception_value_type_generator))
((default_generator)(sequential)))
// clang-format on
RUN_TESTS()
+304
View File
@@ -0,0 +1,304 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
namespace {
test::seed_t initialize_seed(795610904);
UNORDERED_AUTO_TEST (bucket_constructor) {
raii::reset_counts();
bool was_thrown = false;
enable_exceptions();
for (std::size_t i = 0; i < alloc_throw_threshold; ++i) {
try {
map_type m(128);
} catch (...) {
was_thrown = true;
}
}
disable_exceptions();
BOOST_TEST(was_thrown);
}
template <class G> void iterator_range(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
raii::reset_counts();
bool was_thrown = false;
enable_exceptions();
try {
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type(3));
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
{
raii::reset_counts();
bool was_thrown = false;
enable_exceptions();
try {
map_type x(values.begin(), values.end(), allocator_type(3));
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
{
raii::reset_counts();
bool was_thrown = false;
enable_exceptions();
try {
map_type x(
values.begin(), values.end(), values.size(), allocator_type(3));
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
{
raii::reset_counts();
bool was_thrown = false;
enable_exceptions();
try {
map_type x(values.begin(), values.end(), values.size(), hasher(1),
allocator_type(3));
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
}
template <class G> void copy_constructor(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
raii::reset_counts();
bool was_thrown = false;
try {
map_type x(values.begin(), values.end(), 0);
enable_exceptions();
map_type y(x);
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
{
raii::reset_counts();
bool was_thrown = false;
try {
map_type x(values.begin(), values.end(), 0);
enable_exceptions();
map_type y(x, allocator_type(4));
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
}
template <class G> void move_constructor(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
raii::reset_counts();
bool was_thrown = false;
try {
map_type x(values.begin(), values.end(), 0);
enable_exceptions();
map_type y(std::move(x), allocator_type(4));
} catch (...) {
was_thrown = true;
}
disable_exceptions();
BOOST_TEST(was_thrown);
check_raii_counts();
}
}
UNORDERED_AUTO_TEST (initializer_list_bucket_count) {
using value_type = typename map_type::value_type;
std::initializer_list<value_type> values{
value_type{raii{0}, raii{0}},
value_type{raii{1}, raii{1}},
value_type{raii{2}, raii{2}},
value_type{raii{3}, raii{3}},
value_type{raii{4}, raii{4}},
value_type{raii{5}, raii{5}},
value_type{raii{6}, raii{6}},
value_type{raii{6}, raii{6}},
value_type{raii{7}, raii{7}},
value_type{raii{8}, raii{8}},
value_type{raii{9}, raii{9}},
value_type{raii{10}, raii{10}},
value_type{raii{9}, raii{9}},
value_type{raii{8}, raii{8}},
value_type{raii{7}, raii{7}},
value_type{raii{6}, raii{6}},
value_type{raii{5}, raii{5}},
value_type{raii{4}, raii{4}},
value_type{raii{3}, raii{3}},
value_type{raii{2}, raii{2}},
value_type{raii{1}, raii{1}},
value_type{raii{0}, raii{0}},
};
{
raii::reset_counts();
unsigned num_throws = 0;
enable_exceptions();
for (std::size_t i = 0; i < throw_threshold; ++i) {
try {
map_type x(values, 0, hasher(1), key_equal(2), allocator_type(3));
} catch (...) {
++num_throws;
}
}
disable_exceptions();
BOOST_TEST_GT(num_throws, 0u);
check_raii_counts();
}
{
raii::reset_counts();
unsigned num_throws = 0;
enable_exceptions();
for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) {
try {
map_type x(values, allocator_type(3));
} catch (...) {
++num_throws;
}
}
disable_exceptions();
BOOST_TEST_GT(num_throws, 0u);
check_raii_counts();
}
{
raii::reset_counts();
unsigned num_throws = 0;
enable_exceptions();
for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) {
try {
map_type x(values, values.size() * 2, allocator_type(3));
} catch (...) {
++num_throws;
}
}
disable_exceptions();
BOOST_TEST_GT(num_throws, 0u);
check_raii_counts();
}
{
raii::reset_counts();
unsigned num_throws = 0;
enable_exceptions();
for (std::size_t i = 0; i < throw_threshold; ++i) {
try {
map_type x(values, values.size() * 2, hasher(1), allocator_type(3));
} catch (...) {
++num_throws;
}
}
disable_exceptions();
BOOST_TEST_GT(num_throws, 0u);
check_raii_counts();
}
}
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
iterator_range,
((exception_value_type_generator))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
copy_constructor,
((exception_value_type_generator))
((default_generator)(sequential)))
UNORDERED_TEST(
move_constructor,
((exception_value_type_generator))
((default_generator)(sequential)))
// clang-format on
RUN_TESTS()
+263
View File
@@ -0,0 +1,263 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/core/ignore_unused.hpp>
namespace {
test::seed_t initialize_seed(3202923);
struct lvalue_eraser_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
enable_exceptions();
thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
for (auto const& k : values) {
try {
auto count = x.erase(k.first);
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
num_erased += count;
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(x.size(), old_size - num_erased);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
}
} lvalue_eraser;
struct lvalue_eraser_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
enable_exceptions();
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
for (auto const& k : s) {
try {
auto count = x.erase_if(k.first,
[threshold](value_type& v) { return v.second.x_ > threshold; });
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_LE(num_erased, expected_erasures);
BOOST_TEST_EQ(x.size(), old_size - num_erased);
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
}
} lvalue_eraser_if;
struct erase_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
++expected_erasures;
}
});
enable_exceptions();
thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 256; ++i) {
try {
x.erase_if([threshold](value_type& v) {
static std::atomic<std::uint32_t> c{0};
auto t = ++c;
if (should_throw && (t % throw_threshold == 0)) {
throw exception_tag{};
}
return v.second.x_ > threshold;
});
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size()));
}
} erase_if;
struct free_fn_erase_if_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
auto const old_size = x.size();
auto const old_dc = +raii::default_constructor;
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
auto const old_d = +raii::destructor;
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
}
});
auto threshold = max / 2;
enable_exceptions();
thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 256; ++i) {
try {
boost::unordered::erase_if(x, [threshold](value_type& v) {
static std::atomic<std::uint32_t> c{0};
auto t = ++c;
if (should_throw && (t % throw_threshold == 0)) {
throw exception_tag{};
}
return v.second.x_ > threshold;
});
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, old_dc);
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size()));
}
} free_fn_erase_if;
template <class X, class G, class F>
void erase(X*, G gen, F eraser, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
X x(values.size());
x.insert(values.begin(), values.end());
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(raii::destructor, 0u);
test_fuzzy_matches_reference(x, reference_map, rg);
eraser(values, x);
test_fuzzy_matches_reference(x, reference_map, rg);
}
check_raii_counts();
}
boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
erase,
((map))
((exception_value_type_generator)(exception_init_type_generator))
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+445
View File
@@ -0,0 +1,445 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "latch.hpp"
#include "../helpers/generators.hpp"
#include "../helpers/test.hpp"
#include <boost/container_hash/hash.hpp>
#include <boost/core/span.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
#include <type_traits>
#include <vector>
static std::size_t const num_threads =
std::max(2u, std::thread::hardware_concurrency());
std::atomic_bool should_throw{false};
constexpr std::uint32_t throw_threshold = 2500;
constexpr std::uint32_t alloc_throw_threshold = 10;
void enable_exceptions() { should_throw = true; }
void disable_exceptions() { should_throw = false; }
struct exception_tag
{
};
struct stateful_hash
{
int x_ = -1;
static std::atomic<std::uint32_t> c;
void throw_helper() const
{
auto n = ++c;
if (should_throw && ((n + 1) % throw_threshold == 0)) {
throw exception_tag{};
}
}
stateful_hash() {}
stateful_hash(stateful_hash const& rhs) : x_(rhs.x_) {}
stateful_hash(stateful_hash&& rhs) noexcept
{
auto tmp = x_;
x_ = rhs.x_;
rhs.x_ = tmp;
}
stateful_hash(int const x) : x_{x} {}
template <class T> std::size_t operator()(T const& t) const
{
throw_helper();
std::size_t h = static_cast<std::size_t>(x_);
boost::hash_combine(h, t);
return h;
}
bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; }
friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs)
{
os << "{ x_: " << rhs.x_ << " }";
return os;
}
friend void swap(stateful_hash& lhs, stateful_hash& rhs) noexcept
{
if (&lhs != &rhs) {
std::swap(lhs.x_, rhs.x_);
}
}
};
std::atomic<std::uint32_t> stateful_hash::c{0};
struct stateful_key_equal
{
int x_ = -1;
static std::atomic<std::uint32_t> c;
void throw_helper() const
{
auto n = ++c;
if (should_throw && ((n + 1) % throw_threshold == 0)) {
throw exception_tag{};
}
}
stateful_key_equal() = default;
stateful_key_equal(stateful_key_equal const&) = default;
stateful_key_equal(stateful_key_equal&& rhs) noexcept
{
auto tmp = x_;
x_ = rhs.x_;
rhs.x_ = tmp;
}
stateful_key_equal(int const x) : x_{x} {}
template <class T, class U> bool operator()(T const& t, U const& u) const
{
throw_helper();
return t == u;
}
bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; }
friend std::ostream& operator<<(
std::ostream& os, stateful_key_equal const& rhs)
{
os << "{ x_: " << rhs.x_ << " }";
return os;
}
friend void swap(stateful_key_equal& lhs, stateful_key_equal& rhs) noexcept
{
if (&lhs != &rhs) {
std::swap(lhs.x_, rhs.x_);
}
}
};
std::atomic<std::uint32_t> stateful_key_equal::c{0};
static std::atomic<std::uint32_t> allocator_c = {};
template <class T> struct stateful_allocator
{
int x_ = -1;
void throw_helper() const
{
auto n = ++allocator_c;
if (should_throw && ((n + 1) % alloc_throw_threshold == 0)) {
throw exception_tag{};
}
}
using value_type = T;
stateful_allocator() = default;
stateful_allocator(stateful_allocator const&) = default;
stateful_allocator(stateful_allocator&&) = default;
stateful_allocator(int const x) : x_{x} {}
template <class U>
stateful_allocator(stateful_allocator<U> const& rhs) : x_{rhs.x_}
{
}
T* allocate(std::size_t n)
{
throw_helper();
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
bool operator==(stateful_allocator const& rhs) const { return x_ == rhs.x_; }
bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; }
};
struct raii
{
static std::atomic<std::uint32_t> default_constructor;
static std::atomic<std::uint32_t> copy_constructor;
static std::atomic<std::uint32_t> move_constructor;
static std::atomic<std::uint32_t> destructor;
static std::atomic<std::uint32_t> copy_assignment;
static std::atomic<std::uint32_t> move_assignment;
static std::atomic<std::uint32_t> c;
void throw_helper() const
{
auto n = ++c;
if (should_throw && ((n + 1) % throw_threshold == 0)) {
throw exception_tag{};
}
}
int x_ = -1;
raii()
{
throw_helper();
++default_constructor;
}
raii(int const x) : x_{x}
{
throw_helper();
++default_constructor;
}
raii(raii const& rhs) : x_{rhs.x_}
{
throw_helper();
++copy_constructor;
}
raii(raii&& rhs) noexcept : x_{rhs.x_}
{
rhs.x_ = -1;
++move_constructor;
}
~raii() { ++destructor; }
raii& operator=(raii const& rhs)
{
throw_helper();
++copy_assignment;
if (this != &rhs) {
x_ = rhs.x_;
}
return *this;
}
raii& operator=(raii&& rhs) noexcept
{
++move_assignment;
if (this != &rhs) {
x_ = rhs.x_;
rhs.x_ = -1;
}
return *this;
}
friend bool operator==(raii const& lhs, raii const& rhs)
{
return lhs.x_ == rhs.x_;
}
friend bool operator!=(raii const& lhs, raii const& rhs)
{
return !(lhs == rhs);
}
friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; }
friend bool operator!=(raii const& lhs, int const x)
{
return !(lhs.x_ == x);
}
friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; }
friend bool operator!=(int const x, raii const& rhs)
{
return !(rhs.x_ == x);
}
friend std::ostream& operator<<(std::ostream& os, raii const& rhs)
{
os << "{ x_: " << rhs.x_ << " }";
return os;
}
friend std::ostream& operator<<(
std::ostream& os, std::pair<raii const, raii> const& rhs)
{
os << "pair<" << rhs.first << ", " << rhs.second << ">";
return os;
}
static void reset_counts()
{
default_constructor = 0;
copy_constructor = 0;
move_constructor = 0;
destructor = 0;
copy_assignment = 0;
move_assignment = 0;
c = 0;
stateful_hash::c = 0;
stateful_key_equal::c = 0;
allocator_c = 0;
}
friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); }
};
std::atomic<std::uint32_t> raii::default_constructor{0};
std::atomic<std::uint32_t> raii::copy_constructor{0};
std::atomic<std::uint32_t> raii::move_constructor{0};
std::atomic<std::uint32_t> raii::destructor{0};
std::atomic<std::uint32_t> raii::copy_assignment{0};
std::atomic<std::uint32_t> raii::move_assignment{0};
std::atomic<std::uint32_t> raii::c{0};
std::size_t hash_value(raii const& r) noexcept
{
boost::hash<int> hasher;
return hasher(r.x_);
}
struct exception_value_type_generator_type
{
std::pair<raii const, raii> operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
int b = generate(p, rg);
return std::make_pair(raii{a}, raii{b});
}
} exception_value_type_generator;
struct exception_init_type_generator_type
{
std::pair<raii, raii> operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
int b = generate(p, rg);
return std::make_pair(raii{a}, raii{b});
}
} exception_init_type_generator;
template <class T>
std::vector<boost::span<T> > split(
boost::span<T> s, std::size_t const nt /* num threads*/)
{
std::vector<boost::span<T> > subslices;
subslices.reserve(nt);
auto a = s.size() / nt;
auto b = a;
if (s.size() % nt != 0) {
++b;
}
auto num_a = nt;
auto num_b = std::size_t{0};
if (nt * b > s.size()) {
num_a = nt * b - s.size();
num_b = nt - num_a;
}
auto sub_b = s.subspan(0, num_b * b);
auto sub_a = s.subspan(num_b * b);
for (std::size_t i = 0; i < num_b; ++i) {
subslices.push_back(sub_b.subspan(i * b, b));
}
for (std::size_t i = 0; i < num_a; ++i) {
auto const is_last = i == (num_a - 1);
subslices.push_back(
sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a));
}
return subslices;
}
template <class T, class F> void thread_runner(std::vector<T>& values, F f)
{
boost::latch latch(static_cast<std::ptrdiff_t>(num_threads));
std::vector<std::thread> threads;
auto subslices = split<T>(values, num_threads);
for (std::size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([&f, &subslices, i, &latch] {
latch.arrive_and_wait();
auto s = subslices[i];
f(s);
});
}
for (auto& t : threads) {
t.join();
}
}
template <class X, class Y>
void test_matches_reference(X const& x, Y const& reference_map)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
}));
}
template <class X, class Y>
void test_fuzzy_matches_reference(
X const& x, Y const& reference_map, test::random_generator rg)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
if (rg == test::sequential) {
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
}
}));
}
template <class T> using span_value_type = typename T::value_type;
void check_raii_counts()
{
BOOST_TEST_GT(raii::destructor, 0u);
BOOST_TEST_EQ(
raii::default_constructor + raii::copy_constructor + raii::move_constructor,
raii::destructor);
}
template <class T> void shuffle_values(std::vector<T>& v)
{
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(v.begin(), v.end(), g);
}
template <class F>
auto make_random_values(std::size_t count, F f) -> std::vector<decltype(f())>
{
using vector_type = std::vector<decltype(f())>;
vector_type v;
v.reserve(count);
for (std::size_t i = 0; i < count; ++i) {
v.emplace_back(f());
}
return v;
}
+431
View File
@@ -0,0 +1,431 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/core/ignore_unused.hpp>
namespace {
test::seed_t initialize_seed(73987);
struct lvalue_inserter_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
enable_exceptions();
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto const& r : s) {
try {
bool b = x.insert(r);
if (b) {
++num_inserts;
}
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} lvalue_inserter;
struct norehash_lvalue_inserter_type : public lvalue_inserter_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
x.reserve(values.size());
lvalue_inserter_type::operator()(values, x);
BOOST_TEST_GT(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 0u);
}
} norehash_lvalue_inserter;
struct rvalue_inserter_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
BOOST_TEST_EQ(raii::copy_constructor, 0u);
enable_exceptions();
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto& r : s) {
try {
bool b = x.insert(std::move(r));
if (b) {
++num_inserts;
}
} catch (...) {
}
}
});
disable_exceptions();
if (!std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
}
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} rvalue_inserter;
struct norehash_rvalue_inserter_type : public rvalue_inserter_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
x.reserve(values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 0u);
rvalue_inserter_type::operator()(values, x);
if (std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_EQ(raii::move_constructor, x.size());
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
}
}
} norehash_rvalue_inserter;
struct iterator_range_inserter_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
for (std::size_t i = 0; i < 10; ++i) {
x.insert(values[i]);
}
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
try {
x.insert(s.begin(), s.end());
} catch (...) {
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} iterator_range_inserter;
struct lvalue_insert_or_assign_copy_assign_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
for (auto& r : s) {
try {
x.insert_or_assign(r.first, r.second);
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_GT(raii::copy_constructor, 0u);
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} lvalue_insert_or_assign_copy_assign;
struct lvalue_insert_or_assign_move_assign_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
for (auto& r : s) {
try {
x.insert_or_assign(r.first, std::move(r.second));
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_GT(raii::copy_constructor, 0u);
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
}
} lvalue_insert_or_assign_move_assign;
struct rvalue_insert_or_assign_copy_assign_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
for (auto& r : s) {
try {
x.insert_or_assign(std::move(r.first), r.second);
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_GT(raii::copy_constructor, 0u);
BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} rvalue_insert_or_assign_copy_assign;
struct rvalue_insert_or_assign_move_assign_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
for (auto& r : s) {
try {
x.insert_or_assign(std::move(r.first), std::move(r.second));
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
}
} rvalue_insert_or_assign_move_assign;
struct lvalue_insert_or_cvisit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
enable_exceptions();
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto& r : s) {
try {
bool b = x.insert_or_cvisit(
r, [](typename X::value_type const& v) { (void)v; });
if (b) {
++num_inserts;
}
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_GT(num_inserts, 0u);
BOOST_TEST_EQ(raii::default_constructor, 0u);
// don't check move construction count here because of rehashing
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} lvalue_insert_or_cvisit;
struct lvalue_insert_or_visit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
enable_exceptions();
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto& r : s) {
try {
bool b =
x.insert_or_visit(r, [](typename X::value_type& v) { (void)v; });
if (b) {
++num_inserts;
}
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_GT(num_inserts, 0u);
BOOST_TEST_EQ(raii::default_constructor, 0u);
// don't check move construction count here because of rehashing
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} lvalue_insert_or_visit;
struct rvalue_insert_or_cvisit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
enable_exceptions();
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto& r : s) {
try {
bool b = x.insert_or_cvisit(
std::move(r), [](typename X::value_type const& v) { (void)v; });
if (b) {
++num_inserts;
}
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_GT(num_inserts, 0u);
BOOST_TEST_EQ(raii::default_constructor, 0u);
}
} rvalue_insert_or_cvisit;
struct rvalue_insert_or_visit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
std::atomic<std::uint64_t> num_inserts{0};
enable_exceptions();
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto& r : s) {
try {
bool b = x.insert_or_visit(
std::move(r), [](typename X::value_type& v) { (void)v; });
if (b) {
++num_inserts;
}
} catch (...) {
}
}
});
disable_exceptions();
BOOST_TEST_GT(num_inserts, 0u);
BOOST_TEST_EQ(raii::default_constructor, 0u);
if (!std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
}
}
} rvalue_insert_or_visit;
struct iterator_range_insert_or_cvisit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
for (std::size_t i = 0; i < 10; ++i) {
x.insert(values[i]);
}
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
try {
x.insert_or_cvisit(s.begin(), s.end(),
[](typename X::value_type const& v) { (void)v; });
} catch (...) {
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, 0u);
}
} iterator_range_insert_or_cvisit;
struct iterator_range_insert_or_visit_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
for (std::size_t i = 0; i < 10; ++i) {
x.insert(values[i]);
}
enable_exceptions();
thread_runner(values, [&x](boost::span<T> s) {
try {
x.insert_or_visit(s.begin(), s.end(),
[](typename X::value_type const& v) { (void)v; });
} catch (...) {
}
});
disable_exceptions();
BOOST_TEST_EQ(raii::default_constructor, 0u);
}
} iterator_range_insert_or_visit;
template <class X, class G, class F>
void insert(X*, G gen, F inserter, test::random_generator rg)
{
disable_exceptions();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
{
X x;
inserter(values, x);
test_fuzzy_matches_reference(x, reference_map, rg);
}
check_raii_counts();
}
boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
insert,
((map))
((exception_value_type_generator)(exception_init_type_generator))
((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)
(norehash_lvalue_inserter)(norehash_rvalue_inserter)
(lvalue_insert_or_cvisit)(lvalue_insert_or_visit)
(rvalue_insert_or_cvisit)(rvalue_insert_or_visit)
(iterator_range_insert_or_cvisit)(iterator_range_insert_or_visit))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
insert,
((map))
((exception_init_type_generator))
((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)
(rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+78
View File
@@ -0,0 +1,78 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/core/ignore_unused.hpp>
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
namespace {
test::seed_t initialize_seed(223333016);
template <class G> void merge(G gen, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
raii::reset_counts();
auto begin = values.begin();
auto mid = begin + static_cast<std::ptrdiff_t>(values.size() / 2);
auto end = values.end();
{
unsigned num_throws = 0;
for (unsigned i = 0; i < 5 * alloc_throw_threshold; ++i) {
disable_exceptions();
map_type x1(0, hasher(1), key_equal(2), allocator_type(3));
x1.insert(begin, mid);
map_type x2(0, hasher(2), key_equal(1), allocator_type(3));
x2.insert(mid, end);
enable_exceptions();
try {
x1.merge(x2);
} catch (...) {
++num_throws;
}
disable_exceptions();
test_fuzzy_matches_reference(x1, reference_map, rg);
test_fuzzy_matches_reference(x2, reference_map, rg);
}
BOOST_TEST_GT(num_throws, 0u);
}
check_raii_counts();
}
} // namespace
using test::default_generator;
using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
merge,
((exception_value_type_generator))
((default_generator)(sequential)(limited_range)))
// clang-format on
RUN_TESTS()
+61
View File
@@ -0,0 +1,61 @@
// Copyright (C) 2023 Christian Mazakas
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/config/workaround.hpp>
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
test::seed_t initialize_seed{32304628};
using test::default_generator;
using test::limited_range;
using test::sequential;
template <class T>
void swap_call(boost::unordered::concurrent_flat_map<T, T>& x1,
boost::unordered::concurrent_flat_map<T, T>& x2)
{
swap(x1, x2);
}
template <class T>
bool equal_call(boost::unordered::concurrent_flat_map<T, T>& x1,
boost::unordered::concurrent_flat_map<T, T>& x2)
{
return x1 == x2;
}
template <class T>
bool unequal_call(boost::unordered::concurrent_flat_map<T, T>& x1,
boost::unordered::concurrent_flat_map<T, T>& x2)
{
return x1 != x2;
}
#include <boost/unordered/concurrent_flat_map.hpp>
using map_type = boost::unordered::concurrent_flat_map<int, int>;
#if !defined(BOOST_CLANG_VERSION) || \
BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 30700) || \
BOOST_WORKAROUND(BOOST_CLANG_VERSION, >= 30800)
// clang-3.7 seems to have a codegen bug here so we workaround it
UNORDERED_AUTO_TEST (fwd_swap_call) {
map_type x1, x2;
swap_call(x1, x2);
}
#endif
UNORDERED_AUTO_TEST (fwd_equal_call) {
map_type x1, x2;
BOOST_TEST(equal_call(x1, x2));
}
UNORDERED_AUTO_TEST (fwd_unequal_call) {
map_type x1, x2;
BOOST_TEST_NOT(unequal_call(x1, x2));
}
RUN_TESTS()

Some files were not shown because too many files have changed in this diff Show More