From 834580b53948eec553c232dda40beefc68b3e8f9 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 21 Sep 2024 10:58:30 +0200 Subject: [PATCH] added insert_and_visit and similar operations to concurrent containers (#283) --- doc/unordered/changes.adoc | 3 + doc/unordered/concurrent.adoc | 17 + doc/unordered/concurrent_flat_map.adoc | 196 ++++++++- doc/unordered/concurrent_flat_set.adoc | 142 +++++- doc/unordered/concurrent_node_map.adoc | 217 ++++++++- doc/unordered/concurrent_node_set.adoc | 164 ++++++- .../boost/unordered/concurrent_flat_map.hpp | 170 +++++++ .../boost/unordered/concurrent_flat_set.hpp | 119 +++++ .../boost/unordered/concurrent_node_map.hpp | 214 +++++++++ .../boost/unordered/concurrent_node_set.hpp | 163 +++++++ .../detail/concurrent_static_asserts.hpp | 20 +- .../unordered/detail/foa/concurrent_table.hpp | 235 ++++++++-- .../detail/foa/tuple_rotate_right.hpp | 21 +- test/cfoa/emplace_tests.cpp | 133 ++++++ test/cfoa/extract_insert_tests.cpp | 44 +- test/cfoa/insert_tests.cpp | 415 +++++++++++++++++- test/cfoa/try_emplace_tests.cpp | 275 ++++++++++++ 17 files changed, 2473 insertions(+), 75 deletions(-) diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 1bf30623..284e4c81 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -9,6 +9,9 @@ == Release 1.87.0 - Major update * Added concurrent, node-based containers `boost::concurrent_node_map` and `boost::concurrent_node_set`. +* Added `insert_and_visit(x, f1, f2)` and similar operations to concurrent containers, which +allow for visitation of an element right after insertion (by contrast, `insert_or_visit(x, f)` only +visits the element if insertion did _not_ take place). * Made visitation exclusive-locked within certain `boost::concurrent_flat_set` operations to allow for safe mutable modification of elements ({github-pr-url}/265[PR#265^]). diff --git a/doc/unordered/concurrent.adoc b/doc/unordered/concurrent.adoc index a39b2c3e..75fd7922 100644 --- a/doc/unordered/concurrent.adoc +++ b/doc/unordered/concurrent.adoc @@ -132,6 +132,23 @@ will grant visitation functions const/non-const access to the element depending by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result in higher parallelization. For concurrent sets, on the other hand, visitation is always const access. + +Although expected to be used much less frequently, concurrent containers +also provide insertion operations where an element can be visited right after +element creation (in addition to the usual visitation when an equivalent +element already exists): + +[source,c++] +---- + m.insert_and_cvisit(x, + [](const auto& y) { + std::cout<< "(" << y.first << ", " << y.second <<") inserted\n"; + }, + [](const auto& y) { + std::cout<< "(" << y.first << ", " << y.second << ") already exists\n"; + }); +---- + Consult the references of xref:#concurrent_node_set[`boost::concurrent_node_set`], xref:#concurrent_flat_map[`boost::concurrent_node_map`], diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 9b11ec12..82f76ff4 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -169,6 +169,27 @@ namespace boost { template size_type xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_visit](std::initializer_list il, F f); template size_type xref:#concurrent_flat_map_insert_initializer_list_or_visit[insert_or_cvisit](std::initializer_list il, F f); + template + bool xref:#concurrent_flat_map_emplace_and_cvisit[emplace_and_visit](Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_map_emplace_and_cvisit[emplace_and_cvisit](Args&&... args, F1&& f1, F2&& f2); + template bool xref:#concurrent_flat_map_copy_insert_and_cvisit[insert_and_visit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_copy_insert_and_cvisit[insert_and_cvisit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_copy_insert_and_cvisit[insert_and_visit](const init_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_copy_insert_and_cvisit[insert_and_cvisit](const init_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_move_insert_and_cvisit[insert_and_visit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_move_insert_and_cvisit[insert_and_cvisit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_move_insert_and_cvisit[insert_and_visit](init_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_move_insert_and_cvisit[insert_and_cvisit](init_type&& obj, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_map_insert_iterator_range_and_visit[insert_and_visit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_map_insert_iterator_range_and_visit[insert_and_cvisit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_map_insert_initializer_list_and_visit[insert_and_visit](std::initializer_list il, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_map_insert_initializer_list_and_visit[insert_and_cvisit](std::initializer_list il, F1 f1, F2 f2); + template bool xref:#concurrent_flat_map_try_emplace[try_emplace](const key_type& k, Args&&... args); template bool xref:#concurrent_flat_map_try_emplace[try_emplace](key_type&& k, Args&&... args); template bool xref:#concurrent_flat_map_try_emplace[try_emplace](K&& k, Args&&... args); @@ -186,6 +207,19 @@ namespace boost { template bool xref:#concurrent_flat_map_try_emplace_or_cvisit[try_emplace_or_cvisit](K&& k, Args&&... args, F&& f); + template + bool xref:#concurrent_flat_map_try_emplace_and_cvisit[try_emplace_and_visit](const key_type& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_map_try_emplace_and_cvisit[try_emplace_and_cvisit](const key_type& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_map_try_emplace_and_cvisit[try_emplace_and_visit](key_type&& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_map_try_emplace_and_cvisit[try_emplace_and_cvisit](key_type&& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_map_try_emplace_and_cvisit[try_emplace_and_visit](K&& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_map_try_emplace_and_cvisit[try_emplace_and_cvisit](K&& k, Args&&... args, F1&& f1, F2&& f2); + template bool xref:#concurrent_flat_map_insert_or_assign[insert_or_assign](const key_type& k, M&& obj); template bool xref:#concurrent_flat_map_insert_or_assign[insert_or_assign](key_type&& k, M&& obj); template bool xref:#concurrent_flat_map_insert_or_assign[insert_or_assign](K&& k, M&& obj); @@ -394,8 +428,9 @@ user-provided visitation function on the element passed do not introduce data ra * Read access to the element. * Non-mutable modification of the element. -* Mutable modification of the element (if the container operation executing the visitation function is not const -and its name does not contain `cvisit`.) +* Mutable modification of the element: + ** Within a container function accepting two visitation functions, always for the first function. + ** Within a non-const container function whose name does not contain `cvisit`, for the last (or only) visitation function. Any `boost::concurrent_flat_map operation` that inserts or modifies an element `e` synchronizes with the internal invocation of a visitation function on `e`. @@ -1113,7 +1148,113 @@ template size_type insert_or_cvisit(std::initializer_list i Equivalent to [listing,subs="+macros,+quotes"] ----- - this->xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or[c\]visit](il.begin(), il.end(), f); + this->xref:#concurrent_flat_map_insert_iterator_range_or_visit[insert_or_[c\]visit](il.begin(), il.end(), f); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== emplace_and_[c]visit +```c++ +template + bool emplace_and_visit(Args&&... args, F1&& f1, F2&& f2); +template + bool emplace_and_cvisit(Args&&... args, F1&& f1, F2&& f2); +``` + +Inserts an object, constructed with the arguments `args`, in the table if there is no element in the table with an equivalent key, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff `emplace_and_cvisit` is used. + +[horizontal] +Requires:;; `value_type` is constructible from `args`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +The interface is exposition only, as C++ does not allow to declare parameters `f1` and `f2` after a variadic parameter pack. + +--- + +==== Copy insert_and_[c]visit +```c++ +template bool insert_and_visit(const value_type& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(const value_type& obj, F1 f1, F2 f2); +template bool insert_and_visit(const init_type& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(const init_type& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +In a call of the form `insert_and_[c]visit(obj, f1, f2)`, the overloads accepting a `const value_type&` argument participate in overload resolution +only if `std::remove_cv::type>::type` is `value_type`. + +--- + +==== Move insert_and_[c]visit +```c++ +template bool insert_and_visit(value_type&& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(value_type&& obj, F1 f1, F2 f2); +template bool insert_and_visit(init_type&& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(init_type&& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +In a call of the form `insert_and_[c]visit(obj, f1, f2)`, the overloads accepting a `value_type&&` argument participate in overload resolution +only if `std::remove_reference::type` is `value_type`. + +--- + +==== Insert Iterator Range and Visit +```c++ +template + size_type insert_or_visit(InputIterator first, InputIterator last, F1 f1, F2 f2); +template + size_type insert_or_cvisit(InputIterator first, InputIterator last, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_flat_map_emplace_and_cvisit[emplace_and_[c\]visit](*first++, f1, f2); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Initializer List and Visit +```c++ +template + size_type insert_or_visit(std::initializer_list il, F1 f1, F2 f2); +template + size_type insert_or_cvisit(std::initializer_list il, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_flat_map_insert_iterator_range_and_visit[insert_and_[c\]visit](il.begin(), il.end(), f1, f2); ----- [horizontal] @@ -1207,6 +1348,55 @@ The `template` overloads only participate in o --- +==== try_emplace_and_[c]visit +```c++ +template + bool try_emplace_and_visit(const key_type& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_cvisit(const key_type& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_visit(key_type&& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_cvisit(key_type&& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_visit(K&& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_cvisit(K&& k, Args&&... args, F1&& f1, F2&& f2); +``` + +Inserts an element constructed from `k` and `args` into the table if there is no existing element with key `k` contained within it, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; No `value_type` is constructed +if there is an element with an equivalent key; otherwise, the construction is of the form: + ++ +-- +```c++ +// first four overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(std::forward(k)), + std::forward_as_tuple(std::forward(args)...)) + +// last two overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(std::forward(k)), + std::forward_as_tuple(std::forward(args)...)) +``` + +Invalidates pointers and references to elements if a rehashing is issued. + +The interface is exposition only, as C++ does not allow to declare parameters `f1` and `f2` after a variadic parameter pack. + +The `template` 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. + +-- + +--- + ==== insert_or_assign ```c++ template bool insert_or_assign(const key_type& k, M&& obj); diff --git a/doc/unordered/concurrent_flat_set.adoc b/doc/unordered/concurrent_flat_set.adoc index 83327a8f..c519ec49 100644 --- a/doc/unordered/concurrent_flat_set.adoc +++ b/doc/unordered/concurrent_flat_set.adoc @@ -160,6 +160,25 @@ namespace boost { template size_type xref:#concurrent_flat_set_insert_initializer_list_or_visit[insert_or_visit](std::initializer_list il, F f); template size_type xref:#concurrent_flat_set_insert_initializer_list_or_visit[insert_or_cvisit](std::initializer_list il, F f); + template + bool xref:#concurrent_flat_set_emplace_and_cvisit[emplace_and_visit](Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_flat_set_emplace_and_cvisit[emplace_and_cvisit](Args&&... args, F1&& f1, F2&& f2); + template bool xref:#concurrent_flat_set_copy_insert_and_cvisit[insert_and_visit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_set_copy_insert_and_cvisit[insert_and_cvisit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_set_move_insert_and_cvisit[insert_and_visit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_set_move_insert_and_cvisit[insert_and_cvisit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_flat_set_transparent_insert_and_cvisit[insert_and_visit](K&& k, F1 f1, F2 f2); + template bool xref:#concurrent_flat_set_transparent_insert_and_cvisit[insert_and_cvisit](K&& k, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_set_insert_iterator_range_and_visit[insert_and_visit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_set_insert_iterator_range_and_visit[insert_and_cvisit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_set_insert_initializer_list_and_visit[insert_and_visit](std::initializer_list il, F1 f1, F2 f2); + template + size_type xref:#concurrent_flat_set_insert_initializer_list_and_visit[insert_and_cvisit](std::initializer_list il, F1 f1, F2 f2); + size_type xref:#concurrent_flat_set_erase[erase](const key_type& k); template size_type xref:#concurrent_flat_set_erase[erase](const K& k); @@ -355,8 +374,9 @@ user-provided visitation function on the element passed do not introduce data ra * Read access to the element. * Non-mutable modification of the element. -* Mutable modification of the element (if the container operation executing the visitation function is not const -and its name does not contain `cvisit`.) +* Mutable modification of the element: + ** Within a container function accepting two visitation functions, always for the first function. + ** Within a non-const container function whose name does not contain `cvisit`, for the last (or only) visitation function. Any `boost::concurrent_flat_set operation` that inserts or modifies an element `e` synchronizes with the internal invocation of a visitation function on `e`. @@ -1086,7 +1106,123 @@ template size_type insert_or_cvisit(std::initializer_list i Equivalent to [listing,subs="+macros,+quotes"] ----- - this->xref:#concurrent_flat_set_insert_iterator_range_or_visit[insert_or[c\]visit](il.begin(), il.end(), f); + this->xref:#concurrent_flat_set_insert_iterator_range_or_visit[insert_or_[c\]visit](il.begin(), il.end(), f); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== emplace_and_[c]visit +```c++ +template + bool emplace_and_visit(Args&&... args, F1&& f1, F2&& f2); +template + bool emplace_and_cvisit(Args&&... args, F1&& f1, F2&& f2); +``` + +Inserts an object, constructed with the arguments `args`, in the table if there is no element in the table with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is constructible from `args`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +The interface is exposition only, as C++ does not allow to declare parameters `f1` and `f2` after a variadic parameter pack. + +--- + +==== Copy insert_and_[c]visit +```c++ +template bool insert_and_visit(const value_type& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(const value_type& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + +--- + +==== Move insert_and_[c]visit +```c++ +template bool insert_and_visit(value_type&& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(value_type&& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + +--- + +==== Transparent insert_and_[c]visit +```c++ +template bool insert_and_visit(K&& k, F1 f1, F2 f2); +template bool insert_and_cvisit(K&& k, F1 f1, F2 f2); +``` + +Inserts an element constructed from `std::forward(k)` in the container if and only if there is no element in the container with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] from `k`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +These 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. + +--- + +==== Insert Iterator Range and Visit +```c++ +template + size_type insert_and_visit(InputIterator first, InputIterator last, F1 f1, F2 f2); +template + size_type insert_and_cvisit(InputIterator first, InputIterator last, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_flat_set_emplace_and_cvisit[emplace_and_[c\]visit](*first++, f1, f2); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Initializer List and Visit +```c++ +template + size_type insert_and_visit(std::initializer_list il, F1 f1, F2 f2); +template + size_type insert_and_cvisit(std::initializer_list il, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_flat_set_insert_iterator_range_and_visit[insert_and_[c\]visit](il.begin(), il.end(), f1, f2); ----- [horizontal] diff --git a/doc/unordered/concurrent_node_map.adoc b/doc/unordered/concurrent_node_map.adoc index 92829c74..46e45864 100644 --- a/doc/unordered/concurrent_node_map.adoc +++ b/doc/unordered/concurrent_node_map.adoc @@ -175,6 +175,31 @@ namespace boost { template insert_return_type xref:#concurrent_node_map_insert_node_or_visit[insert_or_visit](node_type&& nh, F f); template insert_return_type xref:#concurrent_node_map_insert_node_or_visit[insert_or_cvisit](node_type&& nh, F f); + template + bool xref:#concurrent_node_map_emplace_and_cvisit[emplace_and_visit](Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_map_emplace_and_cvisit[emplace_and_cvisit](Args&&... args, F1&& f1, F2&& f2); + template bool xref:#concurrent_node_map_copy_insert_and_cvisit[insert_and_visit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_copy_insert_and_cvisit[insert_and_cvisit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_copy_insert_and_cvisit[insert_and_visit](const init_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_copy_insert_and_cvisit[insert_and_cvisit](const init_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_move_insert_and_cvisit[insert_and_visit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_move_insert_and_cvisit[insert_and_cvisit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_move_insert_and_cvisit[insert_and_visit](init_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_move_insert_and_cvisit[insert_and_cvisit](init_type&& obj, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_map_insert_iterator_range_and_visit[insert_and_visit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_map_insert_iterator_range_and_visit[insert_and_cvisit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_map_insert_initializer_list_and_visit[insert_and_visit](std::initializer_list il, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_map_insert_initializer_list_and_visit[insert_and_cvisit](std::initializer_list il, F1 f1, F2 f2); + template + insert_return_type xref:#concurrent_node_map_insert_node_and_visit[insert_and_visit](node_type&& nh, F1 f1, F2 f2); + template + insert_return_type xref:#concurrent_node_map_insert_node_and_visit[insert_and_cvisit](node_type&& nh, F1 f1, F2 f2); + template bool xref:#concurrent_node_map_try_emplace[try_emplace](const key_type& k, Args&&... args); template bool xref:#concurrent_node_map_try_emplace[try_emplace](key_type&& k, Args&&... args); template bool xref:#concurrent_node_map_try_emplace[try_emplace](K&& k, Args&&... args); @@ -192,6 +217,20 @@ namespace boost { template bool xref:#concurrent_node_map_try_emplace_or_cvisit[try_emplace_or_cvisit](K&& k, Args&&... args, F&& f); + template + bool xref:#concurrent_node_map_try_emplace_and_cvisit[try_emplace_and_visit](const key_type& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_map_try_emplace_and_cvisit[try_emplace_and_cvisit](const key_type& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_map_try_emplace_and_cvisit[try_emplace_and_visit](key_type&& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_map_try_emplace_and_cvisit[try_emplace_and_cvisit](key_type&& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_map_try_emplace_and_cvisit[try_emplace_and_visit](K&& k, Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_map_try_emplace_and_cvisit[try_emplace_and_cvisit](K&& k, Args&&... args, F1&& f1, F2&& f2); + + template bool xref:#concurrent_node_map_insert_or_assign[insert_or_assign](const key_type& k, M&& obj); template bool xref:#concurrent_node_map_insert_or_assign[insert_or_assign](key_type&& k, M&& obj); template bool xref:#concurrent_node_map_insert_or_assign[insert_or_assign](K&& k, M&& obj); @@ -406,8 +445,9 @@ user-provided visitation function on the element passed do not introduce data ra * Read access to the element. * Non-mutable modification of the element. -* Mutable modification of the element (if the container operation executing the visitation function is not const -and its name does not contain `cvisit`.) +* Mutable modification of the element: + ** Within a container function accepting two visitation functions, always for the first function. + ** Within a non-const container function whose name does not contain `cvisit`, for the last (or only) visitation function. Any `boost::concurrent_node_map operation` that inserts or modifies an element `e` synchronizes with the internal invocation of a visitation function on `e`. @@ -1167,7 +1207,7 @@ template size_type insert_or_cvisit(std::initializer_list i Equivalent to [listing,subs="+macros,+quotes"] ----- - this->xref:#concurrent_node_map_insert_iterator_range_or_visit[insert_or[c\]visit](il.begin(), il.end(), f); + this->xref:#concurrent_node_map_insert_iterator_range_or_visit[insert_or_[c\]visit](il.begin(), il.end(), f); ----- [horizontal] @@ -1196,6 +1236,130 @@ Notes:;; Behavior is undefined if `nh` is not empty and the allocators of `nh` a --- +==== emplace_and_[c]visit +```c++ +template + bool emplace_and_visit(Args&&... args, F1&& f1, F2&& f2); +template + bool emplace_and_cvisit(Args&&... args, F1&& f1, F2&& f2); +``` + +Inserts an object, constructed with the arguments `args`, in the table if there is no element in the table with an equivalent key, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff `emplace_and_cvisit` is used. + +[horizontal] +Requires:;; `value_type` is constructible from `args`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; The interface is exposition only, as C++ does not allow to declare parameters `f1` and `f2` after a variadic parameter pack. + +--- + +==== Copy insert_and_[c]visit +```c++ +template bool insert_and_visit(const value_type& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(const value_type& obj, F1 f1, F2 f2); +template bool insert_and_visit(const init_type& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(const init_type& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; In a call of the form `insert_and_[c]visit(obj, f1, f2)`, the overloads accepting a `const value_type&` argument participate in overload resolution +only if `std::remove_cv::type>::type` is `value_type`. + +--- + +==== Move insert_and_[c]visit +```c++ +template bool insert_and_visit(value_type&& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(value_type&& obj, F1 f1, F2 f2); +template bool insert_and_visit(init_type&& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(init_type&& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; In a call of the form `insert_and_[c]visit(obj, f1, f2)`, the overloads accepting a `value_type&&` argument participate in overload resolution +only if `std::remove_reference::type` is `value_type`. + +--- + +==== Insert Iterator Range and Visit +```c++ +template + size_type insert_or_visit(InputIterator first, InputIterator last, F1 f1, F2 f2); +template + size_type insert_or_cvisit(InputIterator first, InputIterator last, F1 f2, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_node_map_emplace_and_cvisit[emplace_and_[c\]visit](*first++, f1, f2); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Initializer List and Visit +```c++ +template + size_type insert_or_visit(std::initializer_list il, F1 f1, F2 f2); +template + size_type insert_or_cvisit(std::initializer_list il, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_node_map_insert_iterator_range_and_visit[insert_and_[c\]visit](il.begin(), il.end(), f1, f2); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Node and Visit +```c++ +template + insert_return_type insert_and_visit(node_type&& nh, F1 f1, F2 f2); +template + insert_return_type insert_and_cvisit(node_type&& nh, F1 f1, F2 f2); +``` + +If `nh` is empty, does nothing. +Otherwise, inserts the associated element in the table if and only if there is no element in the table with a key equivalent to `nh.key()`, +and then invokes `f1` with a non-const reference to the newly inserted element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff `insert_or_cvisit` is used. + +[horizontal] +Returns:;; An `insert_return_type` object constructed from `inserted` and `node`: + +* If `nh` is empty, `inserted` is `false` and `node` is empty. +* Otherwise if the insertion took place, `inserted` is true and `node` is empty. +* If the insertion failed, `inserted` is false and `node` has the previous value of `nh`. +Throws:;; If an exception is thrown by an operation other than a call to `hasher` or call to `f1` or `f2`, the function has no effect. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Behavior is undefined if `nh` is not empty and the allocators of `nh` and the container are not equal. + +--- + ==== try_emplace ```c++ template bool try_emplace(const key_type& k, Args&&... args); @@ -1278,6 +1442,53 @@ The `template` overloads only participate in o --- +==== try_emplace_and_[c]visit +```c++ +template + bool try_emplace_and_visit(const key_type& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_cvisit(const key_type& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_visit(key_type&& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_cvisit(key_type&& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_visit(K&& k, Args&&... args, F1&& f1, F2&& f2); +template + bool try_emplace_and_cvisit(K&& k, Args&&... args, F1&& f1, F2&& f2); +``` + +Inserts an element constructed from `k` and `args` into the table if there is no existing element with key `k` contained within it, +and then invokes `f1` with a non-const reference to the newly created element. +Otherwise, invokes `f2` with a reference to the equivalent element; such reference is const iff a `*_cvisit` overload is used. + +[horizontal] +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; No `value_type` is constructed +if there is an element with an equivalent key; otherwise, the construction is of the form: + ++ +-- +```c++ +// first four overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(std::forward(k)), + std::forward_as_tuple(std::forward(args)...)) + +// last two overloads +value_type(std::piecewise_construct, + std::forward_as_tuple(std::forward(k)), + std::forward_as_tuple(std::forward(args)...)) +``` + +The interface is exposition only, as C++ does not allow to declare parameter `f1` and `f2` after a variadic parameter pack. + +The `template` 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. + +-- + +--- + ==== insert_or_assign ```c++ template bool insert_or_assign(const key_type& k, M&& obj); diff --git a/doc/unordered/concurrent_node_set.adoc b/doc/unordered/concurrent_node_set.adoc index 71389cc9..2371954a 100644 --- a/doc/unordered/concurrent_node_set.adoc +++ b/doc/unordered/concurrent_node_set.adoc @@ -166,6 +166,29 @@ namespace boost { template insert_return_type xref:#concurrent_node_set_insert_node_or_visit[insert_or_visit](node_type&& nh, F f); template insert_return_type xref:#concurrent_node_set_insert_node_or_visit[insert_or_cvisit](node_type&& nh, F f); + template + bool xref:#concurrent_node_set_emplace_and_cvisit[emplace_and_visit](Args&&... args, F1&& f1, F2&& f2); + template + bool xref:#concurrent_node_set_emplace_and_cvisit[emplace_and_cvisit](Args&&... args, F1&& f1, F2&& f2); + template bool xref:#concurrent_node_set_copy_insert_and_cvisit[insert_and_visit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_set_copy_insert_and_cvisit[insert_and_cvisit](const value_type& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_set_move_insert_and_cvisit[insert_and_visit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_set_move_insert_and_cvisit[insert_and_cvisit](value_type&& obj, F1 f1, F2 f2); + template bool xref:#concurrent_node_set_transparent_insert_and_cvisit[insert_and_visit](K&& k, F1 f1, F2 f2); + template bool xref:#concurrent_node_set_transparent_insert_and_cvisit[insert_and_cvisit](K&& k, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_set_insert_iterator_range_and_visit[insert_and_visit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_set_insert_iterator_range_and_visit[insert_and_cvisit](InputIterator first, InputIterator last, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_set_insert_initializer_list_and_visit[insert_and_visit](std::initializer_list il, F1 f1, F2 f2); + template + size_type xref:#concurrent_node_set_insert_initializer_list_and_visit[insert_and_cvisit](std::initializer_list il, F1 f1, F2 f2); + template + insert_return_type xref:#concurrent_node_set_insert_node_and_visit[insert_and_visit](node_type&& nh, F1 f1, F2 f2); + template + insert_return_type xref:#concurrent_node_set_insert_node_and_visit[insert_and_cvisit](node_type&& nh, F1 f1, F2 f2); + size_type xref:#concurrent_node_set_erase[erase](const key_type& k); template size_type xref:#concurrent_node_set_erase[erase](const K& k); @@ -368,8 +391,9 @@ user-provided visitation function on the element passed do not introduce data ra * Read access to the element. * Non-mutable modification of the element. -* Mutable modification of the element (if the container operation executing the visitation function is not const -and its name does not contain `cvisit`.) +* Mutable modification of the element: + ** Within a container function accepting two visitation functions, always for the first function. + ** Within a non-const container function whose name does not contain `cvisit`, for the last (or only) visitation function. Any `boost::concurrent_node_set operation` that inserts or modifies an element `e` synchronizes with the internal invocation of a visitation function on `e`. @@ -1140,7 +1164,7 @@ template size_type insert_or_cvisit(std::initializer_list i Equivalent to [listing,subs="+macros,+quotes"] ----- - this->xref:#concurrent_node_set_insert_iterator_range_or_visit[insert_or[c\]visit](il.begin(), il.end(), f); + this->xref:#concurrent_node_set_insert_iterator_range_or_visit[insert_or_[c\]visit](il.begin(), il.end(), f); ----- [horizontal] @@ -1169,6 +1193,140 @@ Notes:;; Behavior is undefined if `nh` is not empty and the allocators of `nh` a --- +==== emplace_and_[c]visit +```c++ +template + bool emplace_or_visit(Args&&... args, F1&& f1, F2&& f2); +template + bool emplace_or_cvisit(Args&&... args, F1&& f1, F2&& f2); +``` + +Inserts an object, constructed with the arguments `args`, in the table if there is no element in the table with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is constructible from `args`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; The interface is exposition only, as C++ does not allow to declare parameters `f1` and `f2` after a variadic parameter pack. + +--- + +==== Copy insert_and_[c]visit +```c++ +template bool insert_and_visit(const value_type& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(const value_type& obj, F1 f2, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. + +--- + +==== Move insert_and_[c]visit +```c++ +template bool insert_and_visit(value_type&& obj, F1 f1, F2 f2); +template bool insert_and_cvisit(value_type&& obj, F1 f1, F2 f2); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^]. +Returns:;; `true` if an insert took place. + +Concurrency:;; Blocking on rehashing of `*this`. + +--- + +==== Transparent insert_and_[c]visit +```c++ +template bool insert_and_visit(K&& k, F1 f1, F2 f2); +template bool insert_and_cvisit(K&& k, F1 f1, F2 f2); +``` + +Inserts an element constructed from `std::forward(k)` in the container if and only if there is no element in the container with an equivalent key, +and then invokes `f1` with a const reference to the newly created element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] from `k`. +Returns:;; `true` if an insert took place. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; These 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. + +--- + +==== Insert Iterator Range and Visit +```c++ +template + size_type insert_and_visit(InputIterator first, InputIterator last, F1 f1, F2 f2); +template + size_type insert_and_cvisit(InputIterator first, InputIterator last, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_node_set_emplace_and_cvisit[emplace_and_[c\]visit](*first++, f1, f2); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Initializer List and Visit +```c++ +template + size_type insert_and_visit(std::initializer_list il, F1 f1, F2 f2); +template + size_type insert_and_cvisit(std::initializer_list il, F1 f1, F2 f2); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_node_set_insert_iterator_range_and_visit[insert_and_[c\]visit](il.begin(), il.end(), f1, f2); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Node and Visit +```c++ +template + insert_return_type insert_and_visit(node_type&& nh, F1 f1, F2 f2); +template + insert_return_type insert_and_cvisit(node_type&& nh, F1 f1, F2 f2); +``` + +If `nh` is empty, does nothing. +Otherwise, inserts the associated element in the table if and only if there is no element in the table with a key equivalent to `nh.value()`, +and then invokes `f1` with a const reference to the newly inserted element. +Otherwise, invokes `f2` with a const reference to the equivalent element. + +[horizontal] +Returns:;; An `insert_return_type` object constructed from `inserted` and `node`: + +* If `nh` is empty, `inserted` is `false` and `node` is empty. +* Otherwise if the insertion took place, `inserted` is true and `node` is empty. +* If the insertion failed, `inserted` is false and `node` has the previous value of `nh`. +Throws:;; If an exception is thrown by an operation other than a call to `hasher` or call to `f1` or `f2`, the function has no effect. +Concurrency:;; Blocking on rehashing of `*this`. +Notes:;; Behavior is undefined if `nh` is not empty and the allocators of `nh` and the container are not equal. + +--- + ==== erase ```c++ size_type erase(const key_type& k); diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index b67f2ad3..a91a77e8 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -517,6 +517,80 @@ namespace boost { this->insert_or_cvisit(ilist.begin(), ilist.end(), f); } + template + BOOST_FORCEINLINE auto insert_and_visit(Ty&& value, F1 f1, F2 f2) + -> decltype(table_.insert_and_visit(std::forward(value), f1, f2)) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + return table_.insert_and_visit(std::forward(value), f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_visit(init_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + return table_.insert_and_visit(std::move(obj), f1, f2); + } + + template + void insert_and_visit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_visit(*first, f1, f2); + } + } + + template + void insert_and_visit( + std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + this->insert_and_visit(ilist.begin(), ilist.end(), f1, f2); + } + + template + BOOST_FORCEINLINE auto insert_and_cvisit(Ty&& value, F1 f1, F2 f2) + -> decltype(table_.insert_and_cvisit(std::forward(value), f1, f2)) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(std::forward(value), f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit(init_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(std::move(obj), f1, f2); + } + + template + void insert_and_cvisit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_cvisit(*first, f1, f2); + } + } + + template + void insert_and_cvisit( + std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + this->insert_and_cvisit(ilist.begin(), ilist.end(), f1, f2); + } + template BOOST_FORCEINLINE bool emplace(Args&&... args) { return table_.emplace(std::forward(args)...); @@ -538,6 +612,30 @@ namespace boost { std::forward(arg), std::forward(args)...); } + template + BOOST_FORCEINLINE bool emplace_and_visit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.emplace_and_visit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool emplace_and_cvisit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.emplace_and_cvisit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + template BOOST_FORCEINLINE bool try_emplace(key_type const& k, Args&&... args) { @@ -613,6 +711,78 @@ namespace boost { std::forward(arg), std::forward(args)...); } + template + BOOST_FORCEINLINE bool try_emplace_and_visit( + key_type const& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_visit( + k, std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit( + key_type const& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_cvisit( + k, std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_visit( + key_type&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_visit( + std::move(k), std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit( + key_type&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_cvisit( + std::move(k), std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_visit( + K&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_visit(std::forward(k), + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit( + K&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_cvisit(std::forward(k), + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + BOOST_FORCEINLINE size_type erase(key_type const& k) { return table_.erase(k); diff --git a/include/boost/unordered/concurrent_flat_set.hpp b/include/boost/unordered/concurrent_flat_set.hpp index 7977f91c..05f89e49 100644 --- a/include/boost/unordered/concurrent_flat_set.hpp +++ b/include/boost/unordered/concurrent_flat_set.hpp @@ -517,6 +517,101 @@ namespace boost { this->insert_or_cvisit(ilist.begin(), ilist.end(), f); } + template + BOOST_FORCEINLINE bool insert_and_visit( + value_type const& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_visit(obj, f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_visit(value_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_visit(std::move(obj), f1, f2); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::are_transparent::value, + bool >::type + insert_and_visit(K&& k, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.try_emplace_and_visit(std::forward(k), f1, f2); + } + + template + void insert_and_visit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_visit(*first, f1, f2); + } + } + + template + void insert_and_visit(std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + this->insert_and_visit(ilist.begin(), ilist.end(), f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit( + value_type const& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(obj, f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit(value_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(std::move(obj), f1, f2); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::are_transparent::value, + bool >::type + insert_and_cvisit(K&& k, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.try_emplace_and_cvisit(std::forward(k), f1, f2); + } + + template + void insert_and_cvisit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_cvisit(*first, f1, f2); + } + } + + template + void insert_and_cvisit( + std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + this->insert_and_cvisit(ilist.begin(), ilist.end(), f1, f2); + } + template BOOST_FORCEINLINE bool emplace(Args&&... args) { return table_.emplace(std::forward(args)...); @@ -538,6 +633,30 @@ namespace boost { std::forward(arg), std::forward(args)...); } + template + BOOST_FORCEINLINE bool emplace_and_visit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_CONST_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.emplace_and_visit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool emplace_and_cvisit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_CONST_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.emplace_and_cvisit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + BOOST_FORCEINLINE size_type erase(key_type const& k) { return table_.erase(k); diff --git a/include/boost/unordered/concurrent_node_map.hpp b/include/boost/unordered/concurrent_node_map.hpp index 35abd653..c9fb87cb 100644 --- a/include/boost/unordered/concurrent_node_map.hpp +++ b/include/boost/unordered/concurrent_node_map.hpp @@ -585,6 +585,124 @@ namespace boost { } } + template + BOOST_FORCEINLINE auto insert_and_visit(Ty&& value, F1 f1, F2 f2) + -> decltype(table_.insert_and_visit(std::forward(value), f1, f2)) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + return table_.insert_and_visit(std::forward(value), f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_visit(init_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + return table_.insert_and_visit(std::move(obj), f1, f2); + } + + template + void insert_and_visit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_visit(*first, f1, f2); + } + } + + template + void insert_and_visit( + std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + this->insert_and_visit(ilist.begin(), ilist.end(), f1, f2); + } + + template + insert_return_type insert_and_visit(node_type&& nh, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F2) + using access = detail::foa::node_handle_access; + + if (nh.empty()) { + return {false, node_type{}}; + } + + // Caveat: get_allocator() incurs synchronization (not cheap) + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + if (table_.insert_and_visit(std::move(access::element(nh)), f1, f2)) { + access::reset(nh); + return {true, node_type{}}; + } else { + return {false, std::move(nh)}; + } + } + + template + BOOST_FORCEINLINE auto insert_and_cvisit(Ty&& value, F1 f1, F2 f2) + -> decltype(table_.insert_and_cvisit(std::forward(value), f1, f2)) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(std::forward(value), f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit(init_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(std::move(obj), f1, f2); + } + + template + void insert_and_cvisit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_cvisit(*first, f1, f2); + } + } + + template + void insert_and_cvisit( + std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + this->insert_and_cvisit(ilist.begin(), ilist.end(), f1, f2); + } + + template + insert_return_type insert_and_cvisit(node_type&& nh, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + using access = detail::foa::node_handle_access; + + if (nh.empty()) { + return {false, node_type{}}; + } + + // Caveat: get_allocator() incurs synchronization (not cheap) + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + if (table_.insert_and_cvisit(std::move(access::element(nh)), f1, f2)) { + access::reset(nh); + return {true, node_type{}}; + } else { + return {false, std::move(nh)}; + } + } + template BOOST_FORCEINLINE bool emplace(Args&&... args) { return table_.emplace(std::forward(args)...); @@ -606,6 +724,30 @@ namespace boost { std::forward(arg), std::forward(args)...); } + template + BOOST_FORCEINLINE bool emplace_and_visit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.emplace_and_visit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool emplace_and_cvisit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.emplace_and_cvisit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + template BOOST_FORCEINLINE bool try_emplace(key_type const& k, Args&&... args) { @@ -681,6 +823,78 @@ namespace boost { std::forward(arg), std::forward(args)...); } + template + BOOST_FORCEINLINE bool try_emplace_and_visit( + key_type const& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_visit( + k, std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit( + key_type const& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_cvisit( + k, std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_visit( + key_type&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_visit( + std::move(k), std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit( + key_type&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_cvisit( + std::move(k), std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_visit( + K&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_visit(std::forward(k), + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit( + K&& k, Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.try_emplace_and_cvisit(std::forward(k), + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + BOOST_FORCEINLINE size_type erase(key_type const& k) { return table_.erase(k); diff --git a/include/boost/unordered/concurrent_node_set.hpp b/include/boost/unordered/concurrent_node_set.hpp index 81782a11..ef160606 100644 --- a/include/boost/unordered/concurrent_node_set.hpp +++ b/include/boost/unordered/concurrent_node_set.hpp @@ -585,6 +585,145 @@ namespace boost { } } + template + BOOST_FORCEINLINE bool insert_and_visit( + value_type const& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_visit(obj, f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_visit(value_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_visit(std::move(obj), f1, f2); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::are_transparent::value, + bool >::type + insert_and_visit(K&& k, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.try_emplace_and_visit(std::forward(k), f1, f2); + } + + template + void insert_and_visit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_visit(*first, f1, f2); + } + } + + template + void insert_and_visit(std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + this->insert_and_visit(ilist.begin(), ilist.end(), f1, f2); + } + + template + insert_return_type insert_and_visit(node_type&& nh, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + using access = detail::foa::node_handle_access; + + if (nh.empty()) { + return {false, node_type{}}; + } + + // Caveat: get_allocator() incurs synchronization (not cheap) + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + if (table_.insert_and_visit(std::move(access::element(nh)), f1, f2)) { + access::reset(nh); + return {true, node_type{}}; + } else { + return {false, std::move(nh)}; + } + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit( + value_type const& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(obj, f1, f2); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit(value_type&& obj, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.insert_and_cvisit(std::move(obj), f1, f2); + } + + template + BOOST_FORCEINLINE typename std::enable_if< + detail::are_transparent::value, + bool >::type + insert_and_cvisit(K&& k, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + return table_.try_emplace_and_cvisit(std::forward(k), f1, f2); + } + + template + void insert_and_cvisit( + InputIterator first, InputIterator last, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + for (; first != last; ++first) { + table_.emplace_and_cvisit(*first, f1, f2); + } + } + + template + void insert_and_cvisit( + std::initializer_list ilist, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + this->insert_and_cvisit(ilist.begin(), ilist.end(), f1, f2); + } + + template + insert_return_type insert_and_cvisit(node_type&& nh, F1 f1, F2 f2) + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F1) + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F2) + using access = detail::foa::node_handle_access; + + if (nh.empty()) { + return {false, node_type{}}; + } + + // Caveat: get_allocator() incurs synchronization (not cheap) + BOOST_ASSERT(get_allocator() == nh.get_allocator()); + + if (table_.insert_and_cvisit(std::move(access::element(nh)), f1, f2)) { + access::reset(nh); + return {true, node_type{}}; + } else { + return {false, std::move(nh)}; + } + } + template BOOST_FORCEINLINE bool emplace(Args&&... args) { return table_.emplace(std::forward(args)...); @@ -606,6 +745,30 @@ namespace boost { std::forward(arg), std::forward(args)...); } + template + BOOST_FORCEINLINE bool emplace_and_visit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_CONST_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.emplace_and_visit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool emplace_and_cvisit( + Arg1&& arg1, Arg2&& arg2, Args&&... args) + { + BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_CONST_INVOCABLE( + Arg1, Arg2, Args...) + BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg2, Args...) + return table_.emplace_and_cvisit( + std::forward(arg1), std::forward(arg2), + std::forward(args)...); + } + BOOST_FORCEINLINE size_type erase(key_type const& k) { return table_.erase(k); diff --git a/include/boost/unordered/detail/concurrent_static_asserts.hpp b/include/boost/unordered/detail/concurrent_static_asserts.hpp index 48784ac8..bc76c5b0 100644 --- a/include/boost/unordered/detail/concurrent_static_asserts.hpp +++ b/include/boost/unordered/detail/concurrent_static_asserts.hpp @@ -1,5 +1,5 @@ /* Copyright 2023 Christian Mazakas. - * Copyright 2023 Joaquin M Lopez Munoz. + * Copyright 2023-2024 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) @@ -58,6 +58,24 @@ BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ BOOST_UNORDERED_DETAIL_LAST_ARG(Arg, Args)) +#define BOOST_UNORDERED_DETAIL_PENULTIMATE_ARG(Arg1, Arg2, Args) \ + mp11::mp_at_c, \ + mp11::mp_size>::value - 2> + +#define BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_INVOCABLE( \ + Arg1, Arg2, Args) \ + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE( \ + BOOST_UNORDERED_DETAIL_PENULTIMATE_ARG(Arg1, Arg2, Args)) + +#define BOOST_UNORDERED_STATIC_ASSERT_PENULTIMATE_ARG_CONST_INVOCABLE( \ + Arg1, Arg2, Args) \ + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ + BOOST_UNORDERED_DETAIL_PENULTIMATE_ARG(Arg1, Arg2, Args)) + namespace boost { namespace unordered { namespace detail { diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 43cfceef..d2f0991c 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -215,7 +215,7 @@ struct atomic_integral /* Group-level concurrency protection. It provides a rw mutex plus an * atomic insertion counter for optimistic insertion (see - * unprotected_norehash_emplace_or_visit). + * unprotected_norehash_emplace_and_visit). */ struct group_access @@ -755,6 +755,22 @@ public: try_emplace_args_t{},std::forward(x),std::forward(args)...); } + template + BOOST_FORCEINLINE bool try_emplace_and_visit(Key&& x,Args&&... args) + { + return emplace_and_visit_flast( + group_exclusive{}, + try_emplace_args_t{},std::forward(x),std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool try_emplace_and_cvisit(Key&& x,Args&&... args) + { + return emplace_and_visit_flast( + group_shared{}, + try_emplace_args_t{},std::forward(x),std::forward(args)...); + } + template BOOST_FORCEINLINE bool emplace_or_visit(Args&&... args) { @@ -769,86 +785,121 @@ public: group_shared{},std::forward(args)...); } - template - BOOST_FORCEINLINE bool insert_or_visit(const init_type& x,F&& f) + template + BOOST_FORCEINLINE bool emplace_and_visit(Args&&... args) { - return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); + return construct_and_emplace_and_visit_flast( + group_exclusive{},std::forward(args)...); } - template - BOOST_FORCEINLINE bool insert_or_cvisit(const init_type& x,F&& f) + template + BOOST_FORCEINLINE bool emplace_and_cvisit(Args&&... args) { - return emplace_or_visit_impl(group_shared{},std::forward(f),x); + return construct_and_emplace_and_visit_flast( + group_shared{},std::forward(args)...); } - template - BOOST_FORCEINLINE bool insert_or_visit(init_type&& x,F&& f) + template + BOOST_FORCEINLINE bool insert_or_visit(Value&& x,F&& f) { - return emplace_or_visit_impl( - group_exclusive{},std::forward(f),std::move(x)); + return insert_and_visit( + std::forward(x),[](const value_type&){},std::forward(f)); } - template - BOOST_FORCEINLINE bool insert_or_cvisit(init_type&& x,F&& f) + template + BOOST_FORCEINLINE bool insert_or_cvisit(Value&& x,F&& f) { - return emplace_or_visit_impl( - group_shared{},std::forward(f),std::move(x)); + return insert_and_cvisit( + std::forward(x),[](const value_type&){},std::forward(f)); + } + + template + BOOST_FORCEINLINE bool insert_and_visit(const init_type& x,F1&& f1,F2&& f2) + { + return emplace_and_visit_impl( + group_exclusive{},std::forward(f1),std::forward(f2),x); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit(const init_type& x,F1&& f1,F2&& f2) + { + return emplace_and_visit_impl( + group_shared{},std::forward(f1),std::forward(f2),x); + } + + template + BOOST_FORCEINLINE bool insert_and_visit(init_type&& x,F1&& f1,F2&& f2) + { + return emplace_and_visit_impl( + group_exclusive{},std::forward(f1),std::forward(f2), + std::move(x)); + } + + template + BOOST_FORCEINLINE bool insert_and_cvisit(init_type&& x,F1&& f1,F2&& f2) + { + return emplace_and_visit_impl( + group_shared{},std::forward(f1),std::forward(f2),std::move(x)); } /* SFINAE tilts call ambiguities in favor of init_type */ - template - BOOST_FORCEINLINE auto insert_or_visit(const Value& x,F&& f) + template + BOOST_FORCEINLINE auto insert_and_visit(const Value& x,F1&& f1,F2&& f2) ->enable_if_is_value_type { - return emplace_or_visit_impl(group_exclusive{},std::forward(f),x); + return emplace_and_visit_impl( + group_exclusive{},std::forward(f1),std::forward(f2),x); } - template - BOOST_FORCEINLINE auto insert_or_cvisit(const Value& x,F&& f) + template + BOOST_FORCEINLINE auto insert_and_cvisit(const Value& x,F1&& f1,F2&& f2) ->enable_if_is_value_type { - return emplace_or_visit_impl(group_shared{},std::forward(f),x); + return emplace_and_visit_impl( + group_shared{},std::forward(f1),std::forward(f2),x); } - template - BOOST_FORCEINLINE auto insert_or_visit(Value&& x,F&& f) + template + BOOST_FORCEINLINE auto insert_and_visit(Value&& x,F1&& f1,F2&& f2) ->enable_if_is_value_type { - return emplace_or_visit_impl( - group_exclusive{},std::forward(f),std::move(x)); + return emplace_and_visit_impl( + group_exclusive{},std::forward(f1),std::forward(f2), + std::move(x)); } - template - BOOST_FORCEINLINE auto insert_or_cvisit(Value&& x,F&& f) + template + BOOST_FORCEINLINE auto insert_and_cvisit(Value&& x,F1&& f1,F2&& f2) ->enable_if_is_value_type { - return emplace_or_visit_impl( - group_shared{},std::forward(f),std::move(x)); + return emplace_and_visit_impl( + group_shared{},std::forward(f1),std::forward(f2),std::move(x)); } - template + template BOOST_FORCEINLINE typename std::enable_if< !std::is_same::value, bool >::type - insert_or_visit(element_type&& x, F&& f) + insert_and_visit(element_type&& x,F1&& f1,F2&& f2) { - return emplace_or_visit_impl( - group_exclusive{},std::forward(f),std::move(x)); + return emplace_and_visit_impl( + group_exclusive{},std::forward(f1),std::forward(f2), + std::move(x)); } - template + template BOOST_FORCEINLINE typename std::enable_if< !std::is_same::value, bool >::type - insert_or_cvisit(element_type&& x, F&& f) + insert_and_cvisit(element_type&& x,F1&& f1,F2&& f2) { - return emplace_or_visit_impl( - group_shared{},std::forward(f),std::move(x)); + return emplace_and_visit_impl( + group_shared{},std::forward(f1),std::forward(f2),std::move(x)); } template @@ -1408,23 +1459,59 @@ private: ); } + struct call_construct_and_emplace_and_visit + { + template + BOOST_FORCEINLINE bool operator()( + concurrent_table* this_,Args&&... args)const + { + return this_->construct_and_emplace_and_visit( + std::forward(args)...); + } + }; + + template + BOOST_FORCEINLINE bool construct_and_emplace_and_visit_flast( + GroupAccessMode access_mode,Args&&... args) + { + return mp11::tuple_apply( + call_construct_and_emplace_and_visit{}, + std::tuple_cat( + std::make_tuple(this,access_mode), + tuple_rotate_right<2>( + std::forward_as_tuple(std::forward(args)...)) + ) + ); + } + template BOOST_FORCEINLINE bool construct_and_emplace_or_visit( GroupAccessMode access_mode,F&& f,Args&&... args) + { + return construct_and_emplace_and_visit( + access_mode,[](const value_type&){},std::forward(f), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool construct_and_emplace_and_visit( + GroupAccessMode access_mode,F1&& f1,F2&& f2,Args&&... args) { auto lck=shared_access(); alloc_cted_insert_type x( this->al(),std::forward(args)...); - int res=unprotected_norehash_emplace_or_visit( - access_mode,std::forward(f),type_policy::move(x.value())); + int res=unprotected_norehash_emplace_and_visit( + access_mode,std::forward(f1),std::forward(f2), + type_policy::move(x.value())); if(BOOST_LIKELY(res>=0))return res!=0; lck.unlock(); rehash_if_full(); - return noinline_emplace_or_visit( - access_mode,std::forward(f),type_policy::move(x.value())); + return noinline_emplace_and_visit( + access_mode,std::forward(f1),std::forward(f2), + type_policy::move(x.value())); } template @@ -1442,6 +1529,15 @@ private: access_mode,std::forward(f),std::forward(args)...); } + template + BOOST_NOINLINE bool noinline_emplace_and_visit( + GroupAccessMode access_mode,F1&& f1,F2&& f2,Args&&... args) + { + return emplace_and_visit_impl( + access_mode,std::forward(f1),std::forward(f2), + std::forward(args)...); + } + struct call_emplace_or_visit_impl { template @@ -1465,15 +1561,49 @@ private: ); } + struct call_emplace_and_visit_impl + { + template + BOOST_FORCEINLINE bool operator()( + concurrent_table* this_,Args&&... args)const + { + return this_->emplace_and_visit_impl(std::forward(args)...); + } + }; + + template + BOOST_FORCEINLINE bool emplace_and_visit_flast( + GroupAccessMode access_mode,Args&&... args) + { + return mp11::tuple_apply( + call_emplace_and_visit_impl{}, + std::tuple_cat( + std::make_tuple(this,access_mode), + tuple_rotate_right<2>( + std::forward_as_tuple(std::forward(args)...)) + ) + ); + } + template BOOST_FORCEINLINE bool emplace_or_visit_impl( GroupAccessMode access_mode,F&& f,Args&&... args) + { + return emplace_and_visit_impl( + access_mode,[](const value_type&){},std::forward(f), + std::forward(args)...); + } + + template + BOOST_FORCEINLINE bool emplace_and_visit_impl( + GroupAccessMode access_mode,F1&& f1,F2&& f2,Args&&... args) { for(;;){ { auto lck=shared_access(); - int res=unprotected_norehash_emplace_or_visit( - access_mode,std::forward(f),std::forward(args)...); + int res=unprotected_norehash_emplace_and_visit( + access_mode,std::forward(f1),std::forward(f2), + std::forward(args)...); if(BOOST_LIKELY(res>=0))return res!=0; } rehash_if_full(); @@ -1498,6 +1628,16 @@ private: return true; } + template + BOOST_FORCEINLINE int + unprotected_norehash_emplace_or_visit( + GroupAccessMode access_mode,F&& f,Args&&... args) + { + return unprotected_norehash_emplace_and_visit( + access_mode,[&](const value_type&){}, + std::forward(f),std::forward(args)...); + } + struct reserve_size { reserve_size(concurrent_table& x_):x(x_) @@ -1539,10 +1679,10 @@ private: bool commit_=false; }; - template + template BOOST_FORCEINLINE int - unprotected_norehash_emplace_or_visit( - GroupAccessMode access_mode,F&& f,Args&&... args) + unprotected_norehash_emplace_and_visit( + GroupAccessMode access_mode,F1&& f1,F2&& f2,Args&&... args) { const auto &k=this->key_from(std::forward(args)...); auto hash=this->hash_for(k); @@ -1552,7 +1692,7 @@ private: startover: boost::uint32_t counter=insert_counter(pos0); if(unprotected_visit( - access_mode,k,pos0,hash,std::forward(f)))return 0; + access_mode,k,pos0,hash,std::forward(f2)))return 0; reserve_size rsize(*this); if(BOOST_LIKELY(rsize.succeeded())){ @@ -1572,6 +1712,7 @@ private: this->construct_element(p,std::forward(args)...); rslot.commit(); rsize.commit(); + f1(cast_for(group_exclusive{},type_policy::value_from(*p))); BOOST_UNORDERED_ADD_STATS(this->cstats.insertion,(pb.length())); return 1; } diff --git a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp index c95077b2..bd08842f 100644 --- a/include/boost/unordered/detail/foa/tuple_rotate_right.hpp +++ b/include/boost/unordered/detail/foa/tuple_rotate_right.hpp @@ -1,4 +1,4 @@ -/* Copyright 2023 Joaquin M Lopez Munoz. +/* Copyright 2023-2024 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) @@ -19,27 +19,28 @@ namespace unordered{ namespace detail{ namespace foa{ -template +template using tuple_rotate_right_return_type=mp11::mp_rotate_right_c< typename std::remove_cv::type>::type, - 1 + Offset >; -template -tuple_rotate_right_return_type +template +tuple_rotate_right_return_type tuple_rotate_right_aux(mp11::index_sequence,Tuple&& x) { - return tuple_rotate_right_return_type{ - std::get<(Is+sizeof...(Is)-1)%sizeof...(Is)>(std::forward(x))...}; + return tuple_rotate_right_return_type{ + std::get<(Is+sizeof...(Is)-Offset)%sizeof...(Is)>( + std::forward(x))...}; } -template -tuple_rotate_right_return_type tuple_rotate_right(Tuple&& x) +template +tuple_rotate_right_return_type tuple_rotate_right(Tuple&& x) { using RawTuple=typename std::remove_cv< typename std::remove_reference::type>::type; - return tuple_rotate_right_aux( + return tuple_rotate_right_aux( mp11::make_index_sequence::value>{}, std::forward(x)); } diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp index a7b7682f..b4ac9cf9 100644 --- a/test/cfoa/emplace_tests.cpp +++ b/test/cfoa/emplace_tests.cpp @@ -53,6 +53,34 @@ namespace { return x.emplace_or_cvisit(v.first.x_, v.second.x_, f); } + template + bool member_emplace_and_visit(Container& x, Value& v, F1 f1, F2 f2) + { + return x.emplace_and_visit(v.x_, f1, f2); + } + + template < + typename Container, typename Key, typename Value, typename F1, typename F2> + bool member_emplace_and_visit( + Container& x, std::pair& v, F1 f1, F2 f2) + { + return x.emplace_and_visit(v.first.x_, v.second.x_, f1, f2); + } + + template + bool member_emplace_and_cvisit(Container& x, Value& v, F1 f1, F2 f2) + { + return x.emplace_and_cvisit(v.x_, f1, f2); + } + + template < + typename Container, typename Key, typename Value, typename F1, typename F2> + bool member_emplace_and_cvisit( + Container& x, std::pair& v, F1 f1, F2 f2) + { + return x.emplace_and_cvisit(v.first.x_, v.second.x_, f1, f2); + } + struct lvalue_emplacer_type { template void call_impl(std::vector& values, X& x) @@ -133,6 +161,55 @@ namespace { } } lvalue_emplace_or_cvisit; + struct lvalue_emplace_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = member_emplace_and_cvisit( + x, r, + [&num_inserts_internal](arg_type& v) { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ( + raii::default_constructor, value_type_cardinality * values.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_emplace_and_cvisit; + struct lvalue_emplace_or_visit_type { template void operator()(std::vector& values, X& x) @@ -176,6 +253,55 @@ namespace { } } lvalue_emplace_or_visit; + struct lvalue_emplace_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = member_emplace_and_visit( + x, r, + [&num_inserts_internal](arg_type& v) { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](arg_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ( + raii::default_constructor, value_type_cardinality * values.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size()); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_emplace_and_visit; + struct copy_emplacer_type { template void operator()(std::vector& values, X& x) @@ -312,6 +438,13 @@ UNORDERED_TEST( (lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit)(copy_emplacer)(move_emplacer)) ((default_generator)(sequential)(limited_range))) +UNORDERED_TEST( + emplace, + ((map)(node_map)(set)(node_set)) + ((value_type_generator_factory)(init_type_generator_factory)) + ((lvalue_emplace_and_cvisit)(lvalue_emplace_and_visit)) + ((default_generator)(sequential)(limited_range))) + // clang-format on namespace { diff --git a/test/cfoa/extract_insert_tests.cpp b/test/cfoa/extract_insert_tests.cpp index a3619a22..1b80ce9a 100644 --- a/test/cfoa/extract_insert_tests.cpp +++ b/test/cfoa/extract_insert_tests.cpp @@ -86,7 +86,7 @@ namespace { while (!nh.empty()) { auto& o = out[br2++ % out.size()]; typename X::insert_return_type r; - switch (br3++ % 3) { + switch (br3++ % 5) { case 0: r = o.insert(std::move(nh)); break; @@ -97,13 +97,37 @@ namespace { (void)v2; }); break; - case 2: default: + case 2: r = o.insert_or_cvisit( std::move(nh), [&](arg_visit_type const& v2) { BOOST_ASSERT(test::get_key(v) == test::get_key(v2)); (void)v2; }); break; + case 3: + r = o.insert_and_visit( + std::move(nh), + [&](arg_visit_type& v2) { + BOOST_ASSERT(test::get_key(v) == test::get_key(v2)); + (void)v2; + }, + [&](arg_visit_type& v2) { + BOOST_ASSERT(test::get_key(v) == test::get_key(v2)); + (void)v2; + }); + break; + case 4: default: + r = o.insert_and_cvisit( + std::move(nh), + [&](arg_visit_type& v2) { + BOOST_ASSERT(test::get_key(v) == test::get_key(v2)); + (void)v2; + }, + [&](arg_visit_type const& v2) { + BOOST_ASSERT(test::get_key(v) == test::get_key(v2)); + (void)v2; + }); + break; } BOOST_ASSERT(r.inserted || !r.node.empty()); nh = std::move(r.node); @@ -144,6 +168,20 @@ namespace { BOOST_TEST(!r.inserted); BOOST_TEST(r.node.empty()); } + { + node_type nh; + auto r = x.insert_and_visit( + std::move(nh), [](value_type const&) {}, [](value_type const&) {}); + BOOST_TEST(!r.inserted); + BOOST_TEST(r.node.empty()); + } + { + node_type nh; + auto r = x.insert_and_cvisit( + std::move(nh), [](value_type const&) {}, [](value_type const&) {}); + BOOST_TEST(!r.inserted); + BOOST_TEST(r.node.empty()); + } } } // namespace @@ -156,7 +194,7 @@ UNORDERED_TEST( UNORDERED_TEST( insert_empty_node_tests, - ((test_node_map)(test_node_set))) + ((test_node_map)(test_node_set))) // clang-format on RUN_TESTS() diff --git a/test/cfoa/insert_tests.cpp b/test/cfoa/insert_tests.cpp index 4747a746..50bf5ebb 100644 --- a/test/cfoa/insert_tests.cpp +++ b/test/cfoa/insert_tests.cpp @@ -376,6 +376,62 @@ namespace { } } lvalue_insert_or_cvisit; + struct lvalue_insert_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.insert_and_cvisit( + r, + [&num_inserts_internal](arg_type& v) { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ( + raii::copy_constructor, value_type_cardinality * x.size()); + + if (is_container_node_based::value) { + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + else{ + // 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_and_cvisit; + struct lvalue_insert_or_visit_type { template void operator()(std::vector& values, X& x) @@ -424,6 +480,61 @@ namespace { } } lvalue_insert_or_visit; + struct lvalue_insert_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = + x.insert_and_visit(r, + [&num_inserts_internal](arg_type& v) { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](arg_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ(raii::copy_constructor, value_type_cardinality * x.size()); + + if (is_container_node_based::value) { + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + else{ + // 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_and_visit; + struct rvalue_insert_or_cvisit_type { template void operator()(std::vector& values, X& x) @@ -470,6 +581,66 @@ namespace { } } rvalue_insert_or_cvisit; + struct rvalue_insert_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.insert_and_cvisit( + std::move(r), + [&num_inserts_internal](arg_type& v) { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + + if (std::is_same::value) { + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + else { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE( + raii::move_constructor, value_type_cardinality * x.size()); + } + } + } rvalue_insert_and_cvisit; + struct rvalue_insert_or_visit_type { template void operator()(std::vector& values, X& x) @@ -522,6 +693,65 @@ namespace { } } rvalue_insert_or_visit; + struct rvalue_insert_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.insert_and_visit( + std::move(r), + [&num_inserts_internal](arg_type& v) { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](arg_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + if (std::is_same::value) { + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + else { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE( + raii::move_constructor, value_type_cardinality * x.size()); + } + } + } rvalue_insert_and_visit; + struct iterator_range_insert_or_cvisit_type { template void operator()(std::vector& values, X& x) @@ -561,6 +791,58 @@ namespace { } } iterator_range_insert_or_cvisit; + struct iterator_range_insert_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::vector values2; + values2.reserve(values.size()); + for (auto const& v : values) { + values2.push_back(raii_convertible(v)); + } + + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner( + values2, [&x, &num_inserts, &num_invokes](boost::span s) { + x.insert_and_cvisit(s.begin(), s.end(), + [&num_inserts](arg_type& v) { + (void)v; + ++num_inserts; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ( + raii::default_constructor, value_type_cardinality * values2.size()); +#if (BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 50300) && \ + BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50500)) || \ + (BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 40900) && \ + BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50000)) + // skip test +#else + BOOST_TEST_EQ(raii::copy_constructor, 0u); +#endif + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_and_cvisit; + struct iterator_range_insert_or_visit_type { template void operator()(std::vector& values, X& x) @@ -568,6 +850,13 @@ namespace { static constexpr auto value_type_cardinality = value_cardinality::value; + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + std::vector values2; values2.reserve(values.size()); for (auto const& v : values) { @@ -578,7 +867,7 @@ namespace { thread_runner( values2, [&x, &num_invokes](boost::span s) { x.insert_or_visit(s.begin(), s.end(), - [&num_invokes](typename X::value_type const& v) { + [&num_invokes](arg_type& v) { (void)v; ++num_invokes; }); @@ -600,6 +889,58 @@ namespace { } } iterator_range_insert_or_visit; + struct iterator_range_insert_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + // concurrent_flat_set visit is always const access + using arg_type = typename std::conditional< + std::is_same::value, + typename X::value_type const, + typename X::value_type + >::type; + + std::vector values2; + values2.reserve(values.size()); + for (auto const& v : values) { + values2.push_back(raii_convertible(v)); + } + + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner( + values2, [&x, &num_inserts, &num_invokes](boost::span s) { + x.insert_and_visit(s.begin(), s.end(), + [&num_inserts](arg_type& v) { + (void)v; + ++num_inserts; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ( + raii::default_constructor, value_type_cardinality * values2.size()); +#if (BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 50300) && \ + BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50500)) || \ + (BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 40900) && \ + BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50000)) + // skip test +#else + BOOST_TEST_EQ(raii::copy_constructor, 0u); +#endif + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_and_visit; + template void insert(X*, GF gen_factory, F inserter, test::random_generator rg) { @@ -721,6 +1062,62 @@ namespace { BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); } + + { + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + X x; + + thread_runner(dummy, + [&x, &init_list, &num_inserts, &num_invokes](boost::span) { + x.insert_and_visit(init_list, + [&num_inserts](arg_type& v) { + (void)v; + ++num_inserts; + }, + [&num_invokes](arg_type& v) { + (void)v; + ++num_invokes; + }); + + x.insert_and_cvisit( + init_list, + [&num_inserts](arg_type& v) { + (void)v; + ++num_inserts; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, (init_list.size() - x.size()) + + (num_threads - 1) * init_list.size() + + num_threads * init_list.size()); + BOOST_TEST_EQ(x.size(), reference_cont.size()); + + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) { + BOOST_TEST(reference_cont.contains(get_key(v))); + BOOST_TEST_EQ(v, *reference_cont.find(get_key(v))); + })); + } + + 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_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } } @@ -735,6 +1132,9 @@ namespace { x.insert_or_visit({2, 3}, [](value_type&) {}); x.insert_or_cvisit({3, 4}, [](value_type const&) {}); + + x.insert_and_visit({4, 5}, [](value_type&) {}, [](value_type&) {}); + x.insert_and_cvisit({5, 6}, [](value_type&) {}, [](value_type const&) {}); } boost::unordered::concurrent_flat_map* map; @@ -825,7 +1225,8 @@ using test::sequential; // clang-format off UNORDERED_TEST( insert_initializer_list, - ((map_and_init_list)(node_map_and_init_list)(set_and_init_list))) + ((map_and_init_list)(node_map_and_init_list) + (set_and_init_list)(node_set_and_init_list))) UNORDERED_TEST( insert, @@ -840,6 +1241,16 @@ UNORDERED_TEST( ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( + insert, + ((map)(fancy_map)(node_map)(fancy_node_map) + (set)(fancy_set)(node_set)(fancy_node_set)) + ((value_type_generator_factory)(init_type_generator_factory)) + ((lvalue_insert_and_cvisit)(lvalue_insert_and_visit) + (rvalue_insert_and_cvisit)(rvalue_insert_and_visit) + (iterator_range_insert_and_cvisit)(iterator_range_insert_and_visit)) + ((default_generator)(sequential)(limited_range))) + + UNORDERED_TEST( insert, ((map)(node_map)) ((init_type_generator_factory)) diff --git a/test/cfoa/try_emplace_tests.cpp b/test/cfoa/try_emplace_tests.cpp index e7628f3a..0d551b83 100644 --- a/test/cfoa/try_emplace_tests.cpp +++ b/test/cfoa/try_emplace_tests.cpp @@ -177,6 +177,53 @@ namespace { } } lvalue_try_emplace_or_cvisit; + struct lvalue_try_emplace_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_and_cvisit( + r.first, r.second.x_, + [&num_inserts_internal](typename X::value_type& v) + { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + + if (is_container_node_based::value) { + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + else{ + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + } + + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_try_emplace_and_cvisit; + struct lvalue_try_emplace_or_visit_type { template void operator()(std::vector& values, X& x) @@ -217,6 +264,53 @@ namespace { } } lvalue_try_emplace_or_visit; + struct lvalue_try_emplace_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_and_visit( + r.first, r.second.x_, + [&num_inserts_internal](typename X::value_type& v) + { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + + if (is_container_node_based::value) { + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + else{ + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + } + + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_try_emplace_and_visit; + struct rvalue_try_emplace_or_cvisit_type { template void operator()(std::vector& values, X& x) @@ -258,6 +352,54 @@ namespace { } } rvalue_try_emplace_or_cvisit; + struct rvalue_try_emplace_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_and_cvisit( + std::move(r.first), r.second.x_, + [&num_inserts_internal](typename X::value_type& v) + { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + if (is_container_node_based::value) { + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + else{ + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } + } rvalue_try_emplace_and_cvisit; + struct rvalue_try_emplace_or_visit_type { template void operator()(std::vector& values, X& x) @@ -298,6 +440,53 @@ namespace { } } rvalue_try_emplace_or_visit; + struct rvalue_try_emplace_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_and_visit( + std::move(r.first), r.second.x_, + [&num_inserts_internal](typename X::value_type& v) + { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + if (is_container_node_based::value) { + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + else{ + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } + } rvalue_try_emplace_and_visit; + struct transp_try_emplace_or_cvisit_type { template void operator()(std::vector& values, X& x) @@ -326,6 +515,41 @@ namespace { } } transp_try_emplace_or_cvisit; + struct transp_try_emplace_and_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_and_cvisit( + r.first.x_, r.second.x_, + [&num_inserts_internal](typename X::value_type& v) + { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } transp_try_emplace_and_cvisit; + struct transp_try_emplace_or_visit_type { template void operator()(std::vector& values, X& x) @@ -355,6 +579,42 @@ namespace { } } transp_try_emplace_or_visit; + struct transp_try_emplace_and_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}, num_inserts_internal{0}; + std::atomic num_invokes{0}; + thread_runner(values, + [&x, &num_inserts, &num_inserts_internal, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_and_visit( + r.first.x_, r.second.x_, + [&num_inserts_internal](typename X::value_type& v) + { + (void)v; + ++num_inserts_internal; + }, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, num_inserts_internal); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } transp_try_emplace_and_visit; + template void try_emplace(X*, G gen, F try_emplacer, test::random_generator rg) { @@ -417,6 +677,14 @@ UNORDERED_TEST( (rvalue_try_emplace_or_cvisit)(rvalue_try_emplace_or_visit)) ((default_generator)(sequential)(limited_range))) +UNORDERED_TEST( + try_emplace, + ((map)(node_map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_try_emplace_and_cvisit)(lvalue_try_emplace_and_visit) + (rvalue_try_emplace_and_cvisit)(rvalue_try_emplace_and_visit)) + ((default_generator)(sequential)(limited_range))) + UNORDERED_TEST( try_emplace, ((transp_map)(transp_node_map)) @@ -424,6 +692,13 @@ UNORDERED_TEST( ((transp_try_emplace)(norehash_transp_try_emplace) (transp_try_emplace_or_cvisit)(transp_try_emplace_or_visit)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + try_emplace, + ((transp_map)(transp_node_map)) + ((init_type_generator)) + ((transp_try_emplace_and_cvisit)(transp_try_emplace_and_visit)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS()