From 49f0929466176a18df278ec987ae2bfe76727483 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 10 Sep 2023 18:35:26 +0200 Subject: [PATCH] documented boost::concurrent_flat_set --- doc/unordered/changes.adoc | 13 +- doc/unordered/compliance.adoc | 26 +- doc/unordered/concurrent.adoc | 40 +- doc/unordered/concurrent_flat_set.adoc | 1369 ++++++++++++++++++++++++ doc/unordered/intro.adoc | 9 +- doc/unordered/rationale.adoc | 4 +- doc/unordered/ref.adoc | 1 + doc/unordered/structures.adoc | 8 +- doc/unordered/unordered_flat_set.adoc | 18 +- 9 files changed, 1441 insertions(+), 47 deletions(-) create mode 100644 doc/unordered/concurrent_flat_set.adoc diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 76a5f7fe..f8074c2c 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -6,14 +6,15 @@ :github-pr-url: https://github.com/boostorg/unordered/pull :cpp: C++ -== Release 1.84.0 +== Release 1.84.0 - Major update -* Added `[c]visit_while` operations to `boost::concurrent_map`, +* Added `boost::concurrent_flat_set`. +* Added `[c]visit_while` operations to concurrent containers, with serial and parallel variants. -* Added efficient move construction of `boost::unordered_flat_map` from -`boost::concurrent_flat_map` and vice versa. -* Added debug mode mechanisms for detecting illegal reentrancies into -a `boost::concurrent_flat_map` from user code. +* Added efficient move construction of `boost::unordered_flat_(map|set)` from +`boost::concurrent_flat_(map|set)` and vice versa. +* Added debug-mode mechanisms for detecting illegal reentrancies into +a concurrent container from user code. * Added Boost.Serialization support to all containers and their (non-local) iterator types. * Added support for fancy pointers to open-addressing and concurrent containers. This enables scenarios like the use of Boost.Interprocess allocators to construct containers in shared memory. diff --git a/doc/unordered/compliance.adoc b/doc/unordered/compliance.adoc index eb91ac4f..897c7dfb 100644 --- a/doc/unordered/compliance.adoc +++ b/doc/unordered/compliance.adoc @@ -148,14 +148,14 @@ The main differences with C++ unordered associative containers are: == Concurrent Containers -There is currently no specification in the C++ standard for this or any other concurrent -data structure. `boost::concurrent_flat_map` takes the same template parameters as `std::unordered_map` -and all the maps provided by Boost.Unordered, and its API is modelled after that of -`boost::unordered_flat_map` with the crucial difference that iterators are not provided +There is currently no specification in the C++ standard for this or any other type of concurrent +data structure. The APIs of `boost::concurrent_flat_set` and `boost::concurrent_flat_map` +are modelled after `std::unordered_flat_set` and `std::unordered_flat_map`, respectively, +with the crucial difference that iterators are not provided due to their inherent problems in concurrent scenarios (high contention, prone to deadlocking): -so, `boost::concurrent_flat_map` is technically not a +so, Boost.Unordered concurrent containers are technically not models of https://en.cppreference.com/w/cpp/named_req/Container[Container^], although -it meets all the requirements of https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^] +they meet all the requirements of https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^] containers except those implying iterators. In a non-concurrent unordered container, iterators serve two main purposes: @@ -163,7 +163,7 @@ In a non-concurrent unordered container, iterators serve two main purposes: * Access to an element previously located via lookup. * Container traversal. -In place of iterators, `boost::concurrent_flat_map` uses _internal visitation_ +In place of iterators, `boost::concurrent_flat_set` and `boost::concurrent_flat_map` use _internal visitation_ facilities as a thread-safe substitute. Classical operations returning an iterator to an element already existing in the container, like for instance: @@ -191,15 +191,15 @@ template size_t visit_all(F f); ---- of which there are parallelized versions in C++17 compilers with parallel -algorithm support. In general, the interface of `boost::concurrent_flat_map` -is derived from that of `boost::unordered_flat_map` by a fairly straightforward -process of replacing iterators with visitation where applicable. If -`iterator` and `const_iterator` provide mutable and const access to elements, +algorithm support. In general, the interface of concurrent containers +is derived from that of their non-concurrent counterparts by a fairly straightforward +process of replacing iterators with visitation where applicable. If for +regular maps `iterator` and `const_iterator` provide mutable and const access to elements, respectively, here visitation is granted mutable or const access depending on the constness of the member function used (there are also `*cvisit` overloads for -explicit const visitation). +explicit const visitation); In the case of `boost::concurrent_flat_set`, visitation is always const. -The one notable operation not provided is `operator[]`/`at`, which can be +One notable operation not provided by `boost::concurrent_flat_map` is `operator[]`/`at`, which can be replaced, if in a more convoluted manner, by xref:#concurrent_flat_map_try_emplace_or_cvisit[`try_emplace_or_visit`]. diff --git a/doc/unordered/concurrent.adoc b/doc/unordered/concurrent.adoc index d418cec4..a0b3d998 100644 --- a/doc/unordered/concurrent.adoc +++ b/doc/unordered/concurrent.adoc @@ -3,8 +3,8 @@ :idprefix: concurrent_ -Boost.Unordered currently provides just one concurrent container named `boost::concurrent_flat_map`. -`boost::concurrent_flat_map` is a hash table that allows concurrent write/read access from +Boost.Unordered provides `boost::concurrent_flat_set` and `boost::concurrent_flat_map`, +hash tables that allow concurrent write/read access from different threads without having to implement any synchronzation mechanism on the user's side. [source,c++] @@ -36,16 +36,16 @@ In the example above, threads access `m` without synchronization, just as we'd d single-threaded scenario. In an ideal setting, if a given workload is distributed among _N_ threads, execution is _N_ times faster than with one thread —this limit is never attained in practice due to synchronization overheads and _contention_ (one thread -waiting for another to leave a locked portion of the map), but `boost::concurrent_flat_map` -is designed to perform with very little overhead and typically achieves _linear scaling_ +waiting for another to leave a locked portion of the map), but Boost.Unordered concurrent containers +are designed to perform with very little overhead and typically achieve _linear scaling_ (that is, performance is proportional to the number of threads up to the number of logical cores in the CPU). == Visitation-based API -The first thing a new user of `boost::concurrent_flat_map` will notice is that this -class _does not provide iterators_ (which makes it technically -not a https://en.cppreference.com/w/cpp/named_req/Container[Container^] +The first thing a new user of `boost::concurrent_flat_set` or `boost::concurrent_flat_map` +will notice is that these classes _do not provide iterators_ (which makes then technically +not https://en.cppreference.com/w/cpp/named_req/Container[Containers^] in the C++ standard sense). The reason for this is that iterators are inherently thread-unsafe. Consider this hypothetical code: @@ -73,7 +73,7 @@ m.visit(k, [](const auto& x) { // x is the element with key k (if it exists) ---- The visitation function passed by the user (in this case, a lambda function) -is executed internally by `boost::concurrent_flat_map` in +is executed internally by Boost.Unordered in a thread-safe manner, so it can access the element without worrying about other threads interfering in the process. @@ -112,7 +112,7 @@ if (found) { } ---- -Visitation is prominent in the API provided by `boost::concurrent_flat_map`, and +Visitation is prominent in the API provided by `boost::concurrent_flat_ser` and `boost::concurrent_flat_map`, and many classical operations have visitation-enabled variations: [source,c++] @@ -129,13 +129,17 @@ the element: as a general rule, operations on a `boost::concurrent_flat_map` `m` will grant visitation functions const/non-const access to the element depending on whether `m` is const/non-const. Const access can be always be explicitly requested by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result -in higher parallelization. Consult the xref:#concurrent_flat_map[reference] -for a complete list of available operations. +in higher parallelization. For `boost::concurrent_flat_set`, on the other hand, +visitation is always const access. +Consult the references of +xref:#concurrent_flat_set[`boost::concurrent_flat_set`] and +xref:#concurrent_flat_map[`boost::concurrent_flat_map`] +for the complete list of visitation-enabled operations. == Whole-Table Visitation -In the absence of iterators, `boost::concurrent_flat_map` provides `visit_all` -as an alternative way to process all the elements in the map: +In the absence of iterators, `visit_all` is provided +as an alternative way to process all the elements in the container: [source,c++] ---- @@ -187,12 +191,12 @@ m.erase_if([](auto& x) { `visit_while` and `erase_if` can also be parallelized. Note that, in order to increase efficiency, whole-table visitation operations do not block the table during execution: this implies that elements may be inserted, modified or erased by other threads during visitation. It is -advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map` +advisable not to assume too much about the exact global state of a concurrent container at any point in your program. == Blocking Operations -``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any +``boost::concurrent_flat_set``s and ``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any Boost.Unordered container. Unlike most other operations, these are _blocking_, that is, all other threads are prevented from accesing the tables involved while a copy, assignment, clear or merge operation is in progress. Blocking is taken care of automatically by the library @@ -204,8 +208,10 @@ reserving space in advance of bulk insertions will generally speed up the proces == Interoperability with non-concurrent containers -As their internal data structure is basically the same, `boost::unordered_flat_map` can -be efficiently move-constructed from `boost::concurrent_flat_map` and vice versa. +As open-addressing and concurrent containers are based on the same internal data structure, +`boost::unordered_flat_set` and `boost::unordered_flat_map` can +be efficiently move-constructed from `boost::concurrent_flat_set` and `boost::concurrent_flat_map`, +respectively, and vice versa. This interoperability comes handy in multistage scenarios where parts of the data processing happen in parallel whereas other steps are non-concurrent (or non-modifying). In the following example, we want to construct a histogram from a huge input vector of words: diff --git a/doc/unordered/concurrent_flat_set.adoc b/doc/unordered/concurrent_flat_set.adoc new file mode 100644 index 00000000..c7c67aaa --- /dev/null +++ b/doc/unordered/concurrent_flat_set.adoc @@ -0,0 +1,1369 @@ +[#concurrent_flat_set] +== Class Template concurrent_flat_set + +:idprefix: concurrent_flat_set_ + +`boost::concurrent_flat_set` — A hash table that stores unique values and +allows for concurrent element insertion, erasure, lookup and access +without external synchronization mechanisms. + +Even though it acts as a container, `boost::concurrent_flat_set` +does not model the standard C++ https://en.cppreference.com/w/cpp/named_req/Container[Container^] concept. +In particular, iterators and associated operations (`begin`, `end`, etc.) are not provided. +Element access is done through user-provided _visitation functions_ that are passed +to `concurrent_flat_set` operations where they are executed internally in a controlled fashion. +Such visitation-based API allows for low-contention concurrent usage scenarios. + +The internal data structure of `boost::concurrent_flat_set` is similar to that of +`boost::unordered_flat_set`. As a result of its using open-addressing techniques, +`value_type` must be move-constructible and pointer stability is not kept under rehashing. + +=== Synopsis + +[listing,subs="+macros,+quotes"] +----- +// #include + +namespace boost { + template, + class Pred = std::equal_to, + class Allocator = std::allocator> + class concurrent_flat_set { + public: + // types + using key_type = Key; + using value_type = Key; + using init_type = Key; + using hasher = Hash; + using key_equal = Pred; + using allocator_type = Allocator; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // construct/copy/destroy + xref:#concurrent_flat_set_default_constructor[concurrent_flat_set](); + explicit xref:#concurrent_flat_set_bucket_count_constructor[concurrent_flat_set](size_type n, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + template + xref:#concurrent_flat_set_iterator_range_constructor[concurrent_flat_set](InputIterator f, InputIterator l, + size_type n = _implementation-defined_, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + xref:#concurrent_flat_set_copy_constructor[concurrent_flat_set](const concurrent_flat_set& other); + xref:#concurrent_flat_set_move_constructor[concurrent_flat_set](concurrent_flat_set&& other); + template + xref:#concurrent_flat_set_iterator_range_constructor_with_allocator[concurrent_flat_set](InputIterator f, InputIterator l,const allocator_type& a); + explicit xref:#concurrent_flat_set_allocator_constructor[concurrent_flat_set](const Allocator& a); + xref:#concurrent_flat_set_copy_constructor_with_allocator[concurrent_flat_set](const concurrent_flat_set& other, const Allocator& a); + xref:#concurrent_flat_set_move_constructor_with_allocator[concurrent_flat_set](concurrent_flat_set&& other, const Allocator& a); + xref:#concurrent_flat_set_move_constructor_from_unordered_flat_set[concurrent_flat_set](unordered_flat_set&& other); + xref:#concurrent_flat_set_initializer_list_constructor[concurrent_flat_set](std::initializer_list il, + size_type n = _implementation-defined_ + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + xref:#concurrent_flat_set_bucket_count_constructor_with_allocator[concurrent_flat_set](size_type n, const allocator_type& a); + xref:#concurrent_flat_set_bucket_count_constructor_with_hasher_and_allocator[concurrent_flat_set](size_type n, const hasher& hf, const allocator_type& a); + template + xref:#concurrent_flat_set_iterator_range_constructor_with_bucket_count_and_allocator[concurrent_flat_set](InputIterator f, InputIterator l, size_type n, + const allocator_type& a); + template + xref:#concurrent_flat_set_iterator_range_constructor_with_bucket_count_and_hasher[concurrent_flat_set](InputIterator f, InputIterator l, size_type n, const hasher& hf, + const allocator_type& a); + xref:#concurrent_flat_set_initializer_list_constructor_with_allocator[concurrent_flat_set](std::initializer_list il, const allocator_type& a); + xref:#concurrent_flat_set_initializer_list_constructor_with_bucket_count_and_allocator[concurrent_flat_set](std::initializer_list il, size_type n, + const allocator_type& a); + xref:#concurrent_flat_set_initializer_list_constructor_with_bucket_count_and_hasher_and_allocator[concurrent_flat_set](std::initializer_list il, size_type n, const hasher& hf, + const allocator_type& a); + xref:#concurrent_flat_set_destructor[~concurrent_flat_set](); + concurrent_flat_set& xref:#concurrent_flat_set_copy_assignment[operator++=++](const concurrent_flat_set& other); + concurrent_flat_set& xref:#concurrent_flat_set_move_assignment[operator++=++](concurrent_flat_set&& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); + concurrent_flat_set& xref:#concurrent_flat_set_initializer_list_assignment[operator++=++](std::initializer_list); + allocator_type xref:#concurrent_flat_set_get_allocator[get_allocator]() const noexcept; + + + // visitation + template size_t xref:#concurrent_flat_set_cvisit[visit](const key_type& k, F f) const; + template size_t xref:#concurrent_flat_set_cvisit[cvisit](const key_type& k, F f) const; + template size_t xref:#concurrent_flat_set_cvisit[visit](const K& k, F f) const; + template size_t xref:#concurrent_flat_set_cvisit[cvisit](const K& k, F f) const; + + template size_t xref:#concurrent_flat_set_cvisit_all[visit_all](F f) const; + template size_t xref:#concurrent_flat_set_cvisit_all[cvisit_all](F f) const; + template + void xref:#concurrent_flat_set_parallel_cvisit_all[visit_all](ExecutionPolicy&& policy, F f) const; + template + void xref:#concurrent_flat_set_parallel_cvisit_all[cvisit_all](ExecutionPolicy&& policy, F f) const; + + template bool xref:#concurrent_flat_set_cvisit_while[visit_while](F f) const; + template bool xref:#concurrent_flat_set_cvisit_while[cvisit_while](F f) const; + template + bool xref:#concurrent_flat_set_parallel_cvisit_while[visit_while](ExecutionPolicy&& policy, F f) const; + template + bool xref:#concurrent_flat_set_parallel_cvisit_while[cvisit_while](ExecutionPolicy&& policy, F f) const; + + // capacity + ++[[nodiscard]]++ bool xref:#concurrent_flat_set_empty[empty]() const noexcept; + size_type xref:#concurrent_flat_set_size[size]() const noexcept; + size_type xref:#concurrent_flat_set_max_size[max_size]() const noexcept; + + // modifiers + template bool xref:#concurrent_flat_set_emplace[emplace](Args&&... args); + bool xref:#concurrent_flat_set_copy_insert[insert](const value_type& obj); + bool xref:#concurrent_flat_set_move_insert[insert](value_type&& obj); + template bool xref:#concurrent_flat_set_transparent_insert[insert](K&& k); + template size_type xref:#concurrent_flat_set_insert_iterator_range[insert](InputIterator first, InputIterator last); + size_type xref:#concurrent_flat_set_insert_initializer_list[insert](std::initializer_list il); + + template bool xref:#concurrent_flat_set_emplace_or_cvisit[emplace_or_visit](Args&&... args, F&& f); + template bool xref:#concurrent_flat_set_emplace_or_cvisit[emplace_or_cvisit](Args&&... args, F&& f); + template bool xref:#concurrent_flat_set_copy_insert_or_cvisit[insert_or_visit](const value_type& obj, F f); + template bool xref:#concurrent_flat_set_copy_insert_or_cvisit[insert_or_cvisit](const value_type& obj, F f); + template bool xref:#concurrent_flat_set_move_insert_or_cvisit[insert_or_visit](value_type&& obj, F f); + template bool xref:#concurrent_flat_set_move_insert_or_cvisit[insert_or_cvisit](value_type&& obj, F f); + template bool xref:#concurrent_flat_set_transparent_insert_or_cvisit[insert_or_visit](K&& k, F f); + template bool xref:#concurrent_flat_set_transparent_insert_or_cvisit[insert_or_cvisit](K&& k, F f); + template + size_type xref:#concurrent_flat_set_insert_iterator_range_or_visit[insert_or_visit](InputIterator first, InputIterator last, F f); + template + size_type xref:#concurrent_flat_set_insert_iterator_range_or_visit[insert_or_cvisit](InputIterator first, InputIterator last, F f); + 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); + + size_type xref:#concurrent_flat_set_erase[erase](const key_type& k); + template size_type xref:#concurrent_flat_set_erase[erase](const K& k); + + template size_type xref:#concurrent_flat_set_erase_if_by_key[erase_if](const key_type& k, F f); + template size_type xref:#concurrent_flat_set_erase_if_by_key[erase_if](const K& k, F f); + template size_type xref:#concurrent_flat_set_erase_if[erase_if](F f); + template void xref:#concurrent_flat_set_parallel_erase_if[erase_if](ExecutionPolicy&& policy, F f); + + void xref:#concurrent_flat_set_swap[swap](concurrent_flat_set& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); + void xref:#concurrent_flat_set_clear[clear]() noexcept; + + template + size_type xref:#concurrent_flat_set_merge[merge](concurrent_flat_set& source); + template + size_type xref:#concurrent_flat_set_merge[merge](concurrent_flat_set&& source); + + // observers + hasher xref:#concurrent_flat_set_hash_function[hash_function]() const; + key_equal xref:#concurrent_flat_set_key_eq[key_eq]() const; + + // set operations + size_type xref:#concurrent_flat_set_count[count](const key_type& k) const; + template + size_type xref:#concurrent_flat_set_count[count](const K& k) const; + bool xref:#concurrent_flat_set_contains[contains](const key_type& k) const; + template + bool xref:#concurrent_flat_set_contains[contains](const K& k) const; + + // bucket interface + size_type xref:#concurrent_flat_set_bucket_count[bucket_count]() const noexcept; + + // hash policy + float xref:#concurrent_flat_set_load_factor[load_factor]() const noexcept; + float xref:#concurrent_flat_set_max_load_factor[max_load_factor]() const noexcept; + void xref:#concurrent_flat_set_set_max_load_factor[max_load_factor](float z); + size_type xref:#concurrent_flat_set_max_load[max_load]() const noexcept; + void xref:#concurrent_flat_set_rehash[rehash](size_type n); + void xref:#concurrent_flat_set_reserve[reserve](size_type n); + }; + + // Deduction Guides + template>, + class Pred = std::equal_to>, + class Allocator = std::allocator>> + concurrent_flat_set(InputIterator, InputIterator, typename xref:#concurrent_flat_set_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_set_deduction_guides[__see below__], + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> concurrent_flat_set, Hash, Pred, Allocator>; + + template, class Pred = std::equal_to, + class Allocator = std::allocator> + concurrent_flat_set(std::initializer_list, typename xref:#concurrent_flat_set_deduction_guides[__see below__]::size_type = xref:#concurrent_flat_set_deduction_guides[__see below__], + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> concurrent_flat_set; + + template + concurrent_flat_set(InputIterator, InputIterator, typename xref:#concurrent_flat_set_deduction_guides[__see below__]::size_type, Allocator) + -> concurrent_flat_set, + boost::hash>, + std::equal_to>, Allocator>; + + template + concurrent_flat_set(InputIterator, InputIterator, Allocator) + -> concurrent_flat_set, + boost::hash>, + std::equal_to>, Allocator>; + + template + concurrent_flat_set(InputIterator, InputIterator, typename xref:#concurrent_flat_set_deduction_guides[__see below__]::size_type, Hash, + Allocator) + -> concurrent_flat_set, Hash, + std::equal_to>, Allocator>; + + template + concurrent_flat_set(std::initializer_list, typename xref:#concurrent_flat_set_deduction_guides[__see below__]::size_type, Allocator) + -> concurrent_flat_set, std::equal_to, Allocator>; + + template + concurrent_flat_set(std::initializer_list, Allocator) + -> concurrent_flat_set, std::equal_to, Allocator>; + + template + concurrent_flat_set(std::initializer_list, typename xref:#concurrent_flat_set_deduction_guides[__see below__]::size_type, Hash, Allocator) + -> concurrent_flat_set, Allocator>; + + // Equality Comparisons + template + bool xref:#concurrent_flat_set_operator[operator==](const concurrent_flat_set& x, + const concurrent_flat_set& y); + + template + bool xref:#concurrent_flat_set_operator_2[operator!=](const concurrent_flat_set& x, + const concurrent_flat_set& y); + + // swap + template + void xref:#concurrent_flat_set_swap_2[swap](concurrent_flat_set& x, + concurrent_flat_set& y) + noexcept(noexcept(x.swap(y))); + + // Erasure + template + typename concurrent_flat_set::size_type + xref:#concurrent_flat_set_erase_if_2[erase_if](concurrent_flat_set& c, Predicate pred); +} +----- + +--- + +=== Description + +*Template Parameters* + +[cols="1,1"] +|=== + +|_Key_ +|`Key` must be https://en.cppreference.com/w/cpp/named_req/MoveInsertable[MoveInsertable^] into the container +and https://en.cppreference.com/w/cpp/named_req/Erasable[Erasable^] from the container. + +|_Hash_ +|A unary function object type that acts a hash function for a `Key`. It takes a single argument of type `Key` and returns a value of type `std::size_t`. + +|_Pred_ +|A binary function object that induces an equivalence relation on values of type `Key`. It takes two arguments of type `Key` and returns a value of type `bool`. + +|_Allocator_ +|An allocator whose value type is the same as the table's value type. +`std::allocator_traits::pointer` and `std::allocator_traits::const_pointer` +must be convertible to/from `value_type*` and `const value_type*`, respectively. + +|=== + +The elements of the table are held into an internal _bucket array_. An element is inserted into a bucket determined by its +hash code, but if the bucket is already occupied (a _collision_), an available one in the vicinity of the +original position is used. + +The size of the bucket array can be automatically increased by a call to `insert`/`emplace`, or as a result of calling +`rehash`/`reserve`. The _load factor_ of the table (number of elements divided by number of buckets) is never +greater than `max_load_factor()`, except possibly for small sizes where the implementation may decide to +allow for higher loads. + +If `xref:hash_traits_hash_is_avalanching[hash_is_avalanching]::value` is `true`, the hash function +is used as-is; otherwise, a bit-mixing post-processing stage is added to increase the quality of hashing +at the expense of extra computational cost. + +--- + +=== Concurrency Requirements and Guarantees + +Concurrent invocations of `operator()` on the same const instance of `Hash` or `Pred` are required +to not introduce data races. For `Alloc` being either `Allocator` or any allocator type rebound +from `Allocator`, concurrent invocations of the following operations on the same instance `al` of `Alloc` +are required to not introduce data races: + +* Copy construction from `al` of an allocator rebound from `Alloc` +* `std::allocator_traits::allocate` +* `std::allocator_traits::deallocate` +* `std::allocator_traits::construct` +* `std::allocator_traits::destroy` + +In general, these requirements on `Hash`, `Pred` and `Allocator` are met if these types +are not stateful or if the operations only involve constant access to internal data members. + +With the exception of destruction, concurrent invocations of any operation on the same instance of a +`concurrent_flat_set` do not introduce data races — that is, they are thread-safe. + +If an operation *op* is explicitly designated as _blocking on_ `x`, where `x` is an instance of a `boost::concurrent_flat_set`, +prior blocking operations on `x` synchronize with *op*. So, blocking operations on the same +`concurrent_flat_set` execute sequentially in a multithreaded scenario. + +An operation is said to be _blocking on rehashing of_ ``__x__`` if it blocks on `x` +only when an internal rehashing is issued. + +Access or modification of an element of a `boost::concurrent_flat_set` passed by reference to a +user-provided visitation function do not introduce data races when the visitation function +is executed internally by the `boost::concurrent_flat_set`. + +Any `boost::concurrent_flat_set operation` that inserts or modifies an element `e` +synchronizes with the internal invocation of a visitation function on `e`. + +Visitation functions executed by a `boost::concurrent_flat_set` `x` are not allowed to invoke any operation +on `x`; invoking operations on a different `boost::concurrent_flat_set` instance `y` is allowed only +if concurrent outstanding operations on `y` do not access `x` directly or indirectly. + +--- + +=== Configuration Macros + +==== `BOOST_UNORDERED_DISABLE_REENTRANCY_CHECK` + +In debug builds (more precisely, when +link:../../../assert/doc/html/assert.html#boost_assert_is_void[`BOOST_ASSERT_IS_VOID`^] +is not defined), __container reentrancies__ (illegaly invoking an operation on `m` from within +a function visiting elements of `m`) are detected and signalled through `BOOST_ASSERT_MSG`. +When run-time speed is a concern, the feature can be disabled by globally defining +this macro. + + +=== Constructors + +==== Default Constructor +```c++ +concurrent_flat_set(); +``` + +Constructs an empty table using `hasher()` as the hash function, +`key_equal()` as the key equality predicate and `allocator_type()` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Bucket Count Constructor +```c++ +explicit concurrent_flat_set(size_type n, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash +function, `eql` as the key equality predicate, and `a` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Iterator Range Constructor +[source,c++,subs="+quotes"] +---- +template + concurrent_flat_set(InputIterator f, InputIterator l, + size_type n = _implementation-defined_, + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); +---- + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `eql` as the key equality predicate and `a` as the allocator, and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Copy Constructor +```c++ +concurrent_flat_set(concurrent_flat_set const& other); +``` + +The copy constructor. Copies the contained elements, hash function, predicate and allocator. + +If `Allocator::select_on_container_copy_construction` exists and has the right signature, the allocator will be constructed from its result. + +[horizontal] +Requires:;; `value_type` is copy constructible +Concurrency:;; Blocking on `other`. + +--- + +==== Move Constructor +```c++ +concurrent_flat_set(concurrent_flat_set&& other); +``` + +The move constructor. The internal bucket array of `other` is transferred directly to the new table. +The hash function, predicate and allocator are moved-constructed from `other`. + +[horizontal] +Concurrency:;; Blocking on `other`. + +--- + +==== Iterator Range Constructor with Allocator +```c++ +template + concurrent_flat_set(InputIterator f, InputIterator l, const allocator_type& a); +``` + +Constructs an empty table using `a` as the allocator, with the default hash function and key equality predicate and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; `hasher`, `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Allocator Constructor +```c++ +explicit concurrent_flat_set(Allocator const& a); +``` + +Constructs an empty table, using allocator `a`. + +--- + +==== Copy Constructor with Allocator +```c++ +concurrent_flat_set(concurrent_flat_set const& other, Allocator const& a); +``` + +Constructs a table, copying ``other``'s contained elements, hash function, and predicate, but using allocator `a`. + +[horizontal] +Concurrency:;; Blocking on `other`. + +--- + +==== Move Constructor with Allocator +```c++ +concurrent_flat_set(concurrent_flat_set&& other, Allocator const& a); +``` + +If `a == other.get_allocator()`, the elements of `other` are transferred directly to the new table; +otherwise, elements are moved-constructed from those of `other`. The hash function and predicate are moved-constructed +from `other`, and the allocator is copy-constructed from `a`. + +[horizontal] +Concurrency:;; Blocking on `other`. + +--- + +==== Move Constructor from unordered_flat_set + +```c++ +concurrent_flat_set(unordered_flat_set&& other); +``` + +Move construction from a xref:#unordered_flat_set[`unordered_flat_set`]. +The internal bucket array of `other` is transferred directly to the new container. +The hash function, predicate and allocator are moved-constructed from `other`. + +[horizontal] +Complexity:;; O(`bucket_count()`) + +--- + +==== Initializer List Constructor +[source,c++,subs="+quotes"] +---- +concurrent_flat_set(std::initializer_list il, + size_type n = _implementation-defined_ + const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); +---- + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `eql` as the key equality predicate and `a`, and inserts the elements from `il` into it. + +[horizontal] +Requires:;; If the defaults are used, `hasher`, `key_equal` and `allocator_type` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Bucket Count Constructor with Allocator +```c++ +concurrent_flat_set(size_type n, allocator_type const& a); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, the default hash function and key equality predicate and `a` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; `hasher` and `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Bucket Count Constructor with Hasher and Allocator +```c++ +concurrent_flat_set(size_type n, hasher const& hf, allocator_type const& a); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, the default key equality predicate and `a` as the allocator. + +[horizontal] +Postconditions:;; `size() == 0` +Requires:;; `key_equal` needs to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Iterator Range Constructor with Bucket Count and Allocator +[source,c++,subs="+quotes"] +---- +template + concurrent_flat_set(InputIterator f, InputIterator l, size_type n, const allocator_type& a); +---- + +Constructs an empty table with at least `n` buckets, using `a` as the allocator and default hash function and key equality predicate, and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; `hasher`, `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== Iterator Range Constructor with Bucket Count and Hasher +[source,c++,subs="+quotes"] +---- + template + concurrent_flat_set(InputIterator f, InputIterator l, size_type n, const hasher& hf, + const allocator_type& a); +---- + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `a` as the allocator, with the default key equality predicate, and inserts the elements from `[f, l)` into it. + +[horizontal] +Requires:;; `key_equal` needs to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== initializer_list Constructor with Allocator + +```c++ +concurrent_flat_set(std::initializer_list il, const allocator_type& a); +``` + +Constructs an empty table using `a` and default hash function and key equality predicate, and inserts the elements from `il` into it. + +[horizontal] +Requires:;; `hasher` and `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== initializer_list Constructor with Bucket Count and Allocator + +```c++ +concurrent_flat_set(std::initializer_list il, size_type n, const allocator_type& a); +``` + +Constructs an empty table with at least `n` buckets, using `a` and default hash function and key equality predicate, and inserts the elements from `il` into it. + +[horizontal] +Requires:;; `hasher` and `key_equal` need to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +==== initializer_list Constructor with Bucket Count and Hasher and Allocator + +```c++ +concurrent_flat_set(std::initializer_list il, size_type n, const hasher& hf, + const allocator_type& a); +``` + +Constructs an empty table with at least `n` buckets, using `hf` as the hash function, `a` as the allocator and default key equality predicate,and inserts the elements from `il` into it. + +[horizontal] +Requires:;; `key_equal` needs to be https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^]. + +--- + +=== Destructor + +```c++ +~concurrent_flat_set(); +``` + +[horizontal] +Note:;; The destructor is applied to every element, and all memory is deallocated + +--- + +=== Assignment + +==== Copy Assignment + +```c++ +concurrent_flat_set& operator=(concurrent_flat_set const& other); +``` + +The assignment operator. Destroys previously existing elements, copy-assigns the hash function and predicate from `other`, +copy-assigns the allocator from `other` if `Alloc::propagate_on_container_copy_assignment` exists and `Alloc::propagate_on_container_copy_assignment::value` is `true`, +and finally inserts copies of the elements of `other`. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] +Concurrency:;; Blocking on `*this` and `other`. + +--- + +==== Move Assignment +```c++ +concurrent_flat_set& operator=(concurrent_flat_set&& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); +``` +The move assignment operator. Destroys previously existing elements, swaps the hash function and predicate from `other`, +and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`. +If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to `*this`; +otherwise, inserts move-constructed copies of the elements of `other`. + +[horizontal] +Concurrency:;; Blocking on `*this` and `other`. + +--- + +==== Initializer List Assignment +```c++ +concurrent_flat_set& operator=(std::initializer_list il); +``` + +Assign from values in initializer list. All previously existing elements are destroyed. + +[horizontal] +Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] +Concurrency:;; Blocking on `*this`. + +--- + +=== Visitation + +==== [c]visit + +```c++ +template size_t visit(const key_type& k, F f) const; +template size_t cvisit(const key_type& k, F f) const; +template size_t visit(const K& k, F f) const; +template size_t cvisit(const K& k, F f) const; +``` + +If an element `x` exists with key equivalent to `k`, invokes `f` with a const reference to `x`. + +[horizontal] +Returns:;; The number of elements visited (0 or 1). +Notes:;; 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. + +--- + +==== [c]visit_all + +```c++ +template size_t visit_all(F f) const; +template size_t cvisit_all(F f) const; +``` + +Successively invokes `f` with const references to each of the elements in the table. + +[horizontal] +Returns:;; The number of elements visited. + +--- + +==== Parallel [c]visit_all + +```c++ +template void visit_all(ExecutionPolicy&& policy, F f) const; +template void cvisit_all(ExecutionPolicy&& policy, F f) const; +``` + +Invokes `f` with const references to each of the elements in the table. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +These overloads only participate in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + +--- + +==== [c]visit_while + +```c++ +template bool visit_while(F f) const; +template bool cvisit_while(F f) const; +``` + +Successively invokes `f` with const references to each of the elements in the table until `f` returns `false` +or all the elements are visited. + +[horizontal] +Returns:;; `false` iff `f` ever returns `false`. + +--- + +==== Parallel [c]visit_while + +```c++ +template bool visit_while(ExecutionPolicy&& policy, F f) const; +template bool cvisit_while(ExecutionPolicy&& policy, F f) const; +``` + +Invokes `f` with const references to each of the elements in the table until `f` returns `false` +or all the elements are visited. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Returns:;; `false` iff `f` ever returns `false`. +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +These overloads only participate in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + ++ +Parallelization implies that execution does not necessary finish as soon as `f` returns `false`, and as a result +`f` may be invoked with further elements for which the return value is also `false`. + +--- + +=== Size and Capacity + +==== empty + +```c++ +[[nodiscard]] bool empty() const noexcept; +``` + +[horizontal] +Returns:;; `size() == 0` + +--- + +==== size + +```c++ +size_type size() const noexcept; +``` + +[horizontal] +Returns:;; The number of elements in the table. + +[horizontal] +Notes:;; In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true size of the table right after execution. + +--- + +==== max_size + +```c++ +size_type max_size() const noexcept; +``` + +[horizontal] +Returns:;; `size()` of the largest possible table. + +--- + +=== Modifiers + +==== emplace +```c++ +template bool emplace(Args&&... args); +``` + +Inserts an object, constructed with the arguments `args`, in the table if and only if there is no element in the table with an equivalent key. + +[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. + +--- + +==== Copy Insert +```c++ +bool insert(const value_type& obj); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. + +[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 +```c++ +bool insert(value_type&& obj); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. + +[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 +```c++ +template bool insert(K&& k); +``` + +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. + +[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. + ++ +This overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== Insert Iterator Range +```c++ +template size_type insert(InputIterator first, InputIterator last); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_flat_set_emplace[emplace](*first++); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Initializer List +```c++ +size_type insert(std::initializer_list il); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + this->xref:#concurrent_flat_set_insert_iterator_range[insert](il.begin(), il.end()); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== emplace_or_[c]visit +```c++ +template bool emplace_or_visit(Args&&... args, F&& f); +template bool emplace_or_cvisit(Args&&... args, F&& f); +``` + +Inserts an object, constructed with the arguments `args`, in the table if there is no element in the table with an equivalent key. +Otherwise, invokes `f` 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 a parameter `f` after a variadic parameter pack. + +--- + +==== Copy insert_or_[c]visit +```c++ +template bool insert_or_visit(const value_type& obj, F f); +template bool insert_or_cvisit(const value_type& obj, F f); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. +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`. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + +--- + +==== Move insert_or_[c]visit +```c++ +template bool insert_or_visit(value_type&& obj, F f); +template bool insert_or_cvisit(value_type&& obj, F f); +``` + +Inserts `obj` in the table if and only if there is no element in the table with an equivalent key. +Otherwise, invokes `f` 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_or_[c]visit +```c++ +template bool insert_or_visit(K&& k, F f); +template bool insert_or_cvisit(K&& k, F f); +``` + +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. +Otherwise, invokes `f` 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 or Visit +```c++ +template + size_type insert_or_visit(InputIterator first, InputIterator last, F f); +template + size_type insert_or_cvisit(InputIterator first, InputIterator last, F f); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- + while(first != last) this->xref:#concurrent_flat_set_emplace_or_cvisit[emplace_or_[c\]visit](*first++, f); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== Insert Initializer List or Visit +```c++ +template size_type insert_or_visit(std::initializer_list il, F f); +template size_type insert_or_cvisit(std::initializer_list il, F f); +``` + +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); +----- + +[horizontal] +Returns:;; The number of elements inserted. + +--- + +==== erase +```c++ +size_type erase(const key_type& k); +template size_type erase(const K& k); +``` + +Erases the element with key equivalent to `k` if it exists. + +[horizontal] +Returns:;; The number of elements erased (0 or 1). +Throws:;; Only throws an exception if it is thrown by `hasher` or `key_equal`. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== erase_if by Key +```c++ +template size_type erase_if(const key_type& k, F f); +template size_type erase_if(const K& k, F f); +``` + +Erases the element `x` with key equivalent to `k` if it exists and `f(x)` is `true`. + +[horizontal] +Returns:;; The number of elements erased (0 or 1). +Throws:;; Only throws an exception if it is thrown by `hasher`, `key_equal` or `f`. +Notes:;; The `template` overload only participates in overload resolution if `std::is_execution_policy_v>` is `false`. + ++ +The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + +--- + +==== erase_if +```c++ +template size_type erase_if(F f); +``` + +Successively invokes `f` with references to each of the elements in the table, and erases those for which `f` returns `true`. + +[horizontal] +Returns:;; The number of elements erased. +Throws:;; Only throws an exception if it is thrown by `f`. + +--- + +==== Parallel erase_if +```c++ +template void erase_if(ExecutionPolicy&& policy, F f); +``` + +Invokes `f` with references to each of the elements in the table, and erases those for which `f` returns `true`. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +This overload only participates in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + +--- + +==== swap +```c++ +void swap(concurrent_flat_set& other) + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); +``` + +Swaps the contents of the table with the parameter. + +If `Allocator::propagate_on_container_swap` is declared and `Allocator::propagate_on_container_swap::value` is `true` then the tables' allocators are swapped. Otherwise, swapping with unequal allocators results in undefined behavior. + +[horizontal] +Throws:;; Nothing unless `key_equal` or `hasher` throw on swapping. +Concurrency:;; Blocking on `*this` and `other`. + +--- + +==== clear +```c++ +void clear() noexcept; +``` + +Erases all elements in the table. + +[horizontal] +Postconditions:;; `size() == 0`, `max_load() >= max_load_factor() * bucket_count()` +Concurrency:;; Blocking on `*this`. + +--- + +==== merge +```c++ +template + size_type merge(concurrent_flat_set& source); +template + size_type merge(concurrent_flat_set&& source); +``` + +Move-inserts all the elements from `source` whose key is not already present in `*this`, and erases them from `source`. + +[horizontal] +Returns:;; The number of elements inserted. +Concurrency:;; Blocking on `*this` and `source`. + +--- + +=== Observers + +==== get_allocator +``` +allocator_type get_allocator() const noexcept; +``` + +[horizontal] +Returns:;; The table's allocator. + +--- + +==== hash_function +``` +hasher hash_function() const; +``` + +[horizontal] +Returns:;; The table's hash function. + +--- + +==== key_eq +``` +key_equal key_eq() const; +``` + +[horizontal] +Returns:;; The table's key equality predicate. + +--- + +=== Set Operations + +==== count +```c++ +size_type count(const key_type& k) const; +template + size_type count(const K& k) const; +``` + +[horizontal] +Returns:;; The number of elements with key equivalent to `k` (0 or 1). +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + ++ +In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true state of the table right after execution. + +--- + +==== contains +```c++ +bool contains(const key_type& k) const; +template + bool contains(const K& k) const; +``` + +[horizontal] +Returns:;; A boolean indicating whether or not there is an element with key equal to `k` in the table. +Notes:;; The `template` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type. + ++ +In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true state of the table right after execution. + +--- +=== Bucket Interface + +==== bucket_count +```c++ +size_type bucket_count() const noexcept; +``` + +[horizontal] +Returns:;; The size of the bucket array. + +--- + +=== Hash Policy + +==== load_factor +```c++ +float load_factor() const noexcept; +``` + +[horizontal] +Returns:;; `static_cast(size())/static_cast(bucket_count())`, or `0` if `bucket_count() == 0`. + +--- + +==== max_load_factor + +```c++ +float max_load_factor() const noexcept; +``` + +[horizontal] +Returns:;; Returns the table's maximum load factor. + +--- + +==== Set max_load_factor +```c++ +void max_load_factor(float z); +``` + +[horizontal] +Effects:;; Does nothing, as the user is not allowed to change this parameter. Kept for compatibility with `boost::unordered_set`. + +--- + + +==== max_load + +```c++ +size_type max_load() const noexcept; +``` + +[horizontal] +Returns:;; The maximum number of elements the table can hold without rehashing, assuming that no further elements will be erased. +Note:;; After construction, rehash or clearance, the table's maximum load is at least `max_load_factor() * bucket_count()`. +This number may decrease on erasure under high-load conditions. + ++ +In the presence of concurrent insertion operations, the value returned may not accurately reflect +the true state of the table right after execution. + +--- + +==== rehash +```c++ +void rehash(size_type n); +``` + +Changes if necessary the size of the bucket array so that there are at least `n` buckets, and so that the load factor is less than or equal to the maximum load factor. When applicable, this will either grow or shrink the `bucket_count()` associated with the table. + +When `size() == 0`, `rehash(0)` will deallocate the underlying buckets array. + +Invalidates pointers and references to elements, and changes the order of elements. + +[horizontal] +Throws:;; The function has no effect if an exception is thrown, unless it is thrown by the table's hash function or comparison function. +Concurrency:;; Blocking on `*this`. +--- + +==== reserve +```c++ +void reserve(size_type n); +``` + +Equivalent to `a.rehash(ceil(n / a.max_load_factor()))`. + +Similar to `rehash`, this function can be used to grow or shrink the number of buckets in the table. + +Invalidates pointers and references to elements, and changes the order of elements. + +[horizontal] +Throws:;; The function has no effect if an exception is thrown, unless it is thrown by the table's hash function or comparison function. +Concurrency:;; Blocking on `*this`. + +--- + +=== Deduction Guides +A deduction guide will not participate in overload resolution if any of the following are true: + + - It has an `InputIterator` template parameter and a type that does not qualify as an input iterator is deduced for that parameter. + - It has an `Allocator` template parameter and a type that does not qualify as an allocator is deduced for that parameter. + - It has a `Hash` template parameter and an integral type or a type that qualifies as an allocator is deduced for that parameter. + - It has a `Pred` template parameter and a type that qualifies as an allocator is deduced for that parameter. + +A `size_­type` parameter type in a deduction guide refers to the `size_­type` member type of the +container type deduced by the deduction guide. Its default value coincides with the default value +of the constructor selected. + +==== __iter-value-type__ +[listings,subs="+macros,+quotes"] +----- +template + using __iter-value-type__ = + typename std::iterator_traits::value_type; // exposition only +----- + +=== Equality Comparisons + +==== operator== +```c++ +template + bool operator==(const concurrent_flat_set& x, + const concurrent_flat_set& y); +``` + +Returns `true` if `x.size() == y.size()` and for every element in `x`, there is an element in `y` with the same key, with an equal value (using `operator==` to compare the value types). + +[horizontal] +Concurrency:;; Blocking on `x` and `y`. +Notes:;; Behavior is undefined if the two tables don't have equivalent equality predicates. + +--- + +==== operator!= +```c++ +template + bool operator!=(const concurrent_flat_set& x, + const concurrent_flat_set& y); +``` + +Returns `false` if `x.size() == y.size()` and for every element in `x`, there is an element in `y` with the same key, with an equal value (using `operator==` to compare the value types). + +[horizontal] +Concurrency:;; Blocking on `x` and `y`. +Notes:;; Behavior is undefined if the two tables don't have equivalent equality predicates. + +--- + +=== Swap +```c++ +template + void swap(concurrent_flat_set& x, + concurrent_flat_set& y) + noexcept(noexcept(x.swap(y))); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- +x.xref:#concurrent_flat_set_swap[swap](y); +----- + +--- + +=== erase_if +```c++ +template + typename concurrent_flat_set::size_type + erase_if(concurrent_flat_set& c, Predicate pred); +``` + +Equivalent to +[listing,subs="+macros,+quotes"] +----- +c.xref:#concurrent_flat_set_erase_if[erase_if](pred); +----- + +=== Serialization + +``concurrent_flat_set``s can be archived/retrieved by means of +link:../../../serialization/index.html[Boost.Serialization^] using the API provided +by this library. Both regular and XML archives are supported. + +==== Saving an concurrent_flat_set to an archive + +Saves all the elements of a `concurrent_flat_set` `x` to an archive (XML archive) `ar`. + +[horizontal] +Requires:;; `value_type` is serializable (XML serializable), and it supports Boost.Serialization +`save_construct_data`/`load_construct_data` protocol (automatically suported by +https://en.cppreference.com/w/cpp/named_req/DefaultConstructible[DefaultConstructible^] +types). +Concurrency:;; Blocking on `x`. + +--- + +==== Loading an concurrent_flat_set from an archive + +Deletes all preexisting elements of a `concurrent_flat_set` `x` and inserts +from an archive (XML archive) `ar` restored copies of the elements of the +original `concurrent_flat_set` `other` saved to the storage read by `ar`. + +[horizontal] +Requires:;; `x.key_equal()` is functionally equivalent to `other.key_equal()`. +Concurrency:;; Blocking on `x`. diff --git a/doc/unordered/intro.adoc b/doc/unordered/intro.adoc index 46bc899c..2c6dfbd3 100644 --- a/doc/unordered/intro.adoc +++ b/doc/unordered/intro.adoc @@ -44,7 +44,8 @@ boost::unordered_flat_map ^.^h|*Concurrent* ^| -^| `boost::concurrent_flat_map` +^| `boost::concurrent_flat_set` + +`boost::concurrent_flat_map` |=== @@ -56,9 +57,8 @@ in the market within the technical constraints imposed by the required standard interface to accommodate the implementation. There are two variants: **flat** (the fastest) and **node-based**, which provide pointer stability under rehashing at the expense of being slower. -* Finally, `boost::concurrent_flat_map` (the only **concurrent container** provided -at present) is a hashmap designed and implemented to be used in high-performance -multithreaded scenarios. Its interface is radically different from that of regular C++ containers. +* Finally, **concurrent containers** are designed and implemented to be used in high-performance +multithreaded scenarios. Their interface is radically different from that of regular C++ containers. All sets and maps in Boost.Unordered are instantiatied similarly as `std::unordered_set` and `std::unordered_map`, respectively: @@ -73,6 +73,7 @@ namespace boost { class Alloc = std::allocator > class unordered_set; // same for unordered_multiset, unordered_flat_set, unordered_node_set + // and concurrent_flat_set template < class Key, class Mapped, diff --git a/doc/unordered/rationale.adoc b/doc/unordered/rationale.adoc index fb7d8dd7..256800ab 100644 --- a/doc/unordered/rationale.adoc +++ b/doc/unordered/rationale.adoc @@ -121,7 +121,7 @@ for Visual Studio on an x64-mode Intel CPU with SSE2 and for GCC on an IBM s390x == Concurrent Containers The same data structure used by Boost.Unordered open-addressing containers has been chosen -also as the foundation of `boost::concurrent_flat_map`: +also as the foundation of `boost::concurrent_flat_set` and `boost::concurrent_flat_map`: * Open-addressing is faster than closed-addressing alternatives, both in non-concurrent and concurrent scenarios. @@ -135,7 +135,7 @@ and vice versa. === Hash Function and Platform Interoperability -`boost::concurrent_flat_map` makes the same decisions and provides the same guarantees +Concurrent containers make the same decisions and provide the same guarantees as Boost.Unordered open-addressing containers with regards to xref:#rationale_hash_function[hash function defaults] and xref:#rationale_platform_interoperability[platform interoperability]. diff --git a/doc/unordered/ref.adoc b/doc/unordered/ref.adoc index 6a9673da..08743fa6 100644 --- a/doc/unordered/ref.adoc +++ b/doc/unordered/ref.adoc @@ -11,3 +11,4 @@ include::unordered_flat_set.adoc[] include::unordered_node_map.adoc[] include::unordered_node_set.adoc[] include::concurrent_flat_map.adoc[] +include::concurrent_flat_set.adoc[] diff --git a/doc/unordered/structures.adoc b/doc/unordered/structures.adoc index 9859c39e..2da13548 100644 --- a/doc/unordered/structures.adoc +++ b/doc/unordered/structures.adoc @@ -67,8 +67,8 @@ xref:#rationale_closed_addressing_containers[corresponding section]. == Open-addressing Containers -The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and -`boost:unordered_flat_set`/`unordered_node_set`. +The diagram shows the basic internal layout of `boost::unordered_flat_set`/`unordered_node_set` and +`boost:unordered_flat_map`/`unordered_node_map`. [#img-foa-layout] @@ -76,7 +76,7 @@ The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unor image::foa.png[align=center] As with all open-addressing containers, elements (or pointers to the element nodes in the case of -`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array. +`boost::unordered_node_set` and `boost::unordered_node_map`) are stored directly in the bucket array. This array is logically divided into 2^_n_^ _groups_ of 15 elements each. In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^ 16-byte words. @@ -129,7 +129,7 @@ xref:#rationale_open_addresing_containers[corresponding section]. == Concurrent Containers -`boost::concurrent_flat_map` uses the basic +`boost::concurrent_flat_set` and `boost::concurrent_flat_map` use the basic xref:#structures_open_addressing_containers[open-addressing layout] described above augmented with synchronization mechanisms. diff --git a/doc/unordered/unordered_flat_set.adoc b/doc/unordered/unordered_flat_set.adoc index 9fbad161..c52b9159 100644 --- a/doc/unordered/unordered_flat_set.adoc +++ b/doc/unordered/unordered_flat_set.adoc @@ -71,7 +71,7 @@ namespace boost { xref:#unordered_flat_set_iterator_range_constructor_with_allocator[unordered_flat_set](InputIterator f, InputIterator l, const allocator_type& a); explicit xref:#unordered_flat_set_allocator_constructor[unordered_flat_set](const Allocator& a); xref:#unordered_flat_set_copy_constructor_with_allocator[unordered_flat_set](const unordered_flat_set& other, const Allocator& a); - xref:#unordered_flat_set_move_constructor_with_allocator[unordered_flat_set](unordered_flat_set&& other, const Allocator& a); + xref:#unordered_flat_set_move_constructor_from_concurrent_flat_set[unordered_flat_set](concurrent_flat_set&& other); xref:#unordered_flat_set_initializer_list_constructor[unordered_flat_set](std::initializer_list il, size_type n = _implementation-defined_ const hasher& hf = hasher(), @@ -422,6 +422,22 @@ from `other`, and the allocator is copy-constructed from `a`. --- +==== Move Constructor from concurrent_flat_set + +```c++ +unordered_flat_set(concurrent_flat_set&& other); +``` + +Move construction from a xref:#concurrent_flat_set[`concurrent_flat_set`]. +The internal bucket array of `other` is transferred directly to the new container. +The hash function, predicate and allocator are moved-constructed from `other`. + +[horizontal] +Complexity:;; Constant time. +Concurrency:;; Blocking on `other`. + +--- + ==== Initializer List Constructor [source,c++,subs="+quotes"] ----