forked from boostorg/unordered
207 lines
8.8 KiB
Plaintext
207 lines
8.8 KiB
Plaintext
[#compliance]
|
|
= Standard Compliance
|
|
|
|
:idprefix: compliance_
|
|
|
|
:cpp: C++
|
|
|
|
== Closed-addressing Containers
|
|
|
|
`unordered_[multi]set` and `unordered_[multi]map` are intended to provide a conformant
|
|
implementation of the {cpp}20 standard that will work with {cpp}98 upwards.
|
|
This wide compatibility does mean some compromises have to be made.
|
|
With a compiler and library that fully support {cpp}11, the differences should
|
|
be minor.
|
|
|
|
=== Move Emulation
|
|
|
|
Support for move semantics is implemented using Boost.Move. If rvalue
|
|
references are available it will use them, but if not it uses a close,
|
|
but imperfect emulation. On such compilers:
|
|
|
|
* Non-copyable objects can be stored in the containers.
|
|
They can be constructed in place using `emplace`, or if they support
|
|
Boost.Move, moved into place.
|
|
* The containers themselves are not movable.
|
|
* Argument forwarding is not perfect.
|
|
|
|
=== Use of Allocators
|
|
|
|
{cpp}11 introduced a new allocator system. It's backwards compatible due to
|
|
the lax requirements for allocators in the old standard, but might need
|
|
some changes for allocators which worked with the old versions of the
|
|
unordered containers.
|
|
It uses a traits class, `allocator_traits` to handle the allocator
|
|
adding extra functionality, and making some methods and types optional.
|
|
During development a stable release of
|
|
`allocator_traits` wasn't available so an internal partial implementation
|
|
is always used in this version. Hopefully a future version will use the
|
|
standard implementation where available.
|
|
|
|
The member functions `construct`, `destroy` and `max_size` are now
|
|
optional, if they're not available a fallback is used.
|
|
A full implementation of `allocator_traits` requires sophisticated
|
|
member function detection so that the fallback is used whenever the
|
|
member function call is not well formed.
|
|
This requires support for SFINAE expressions, which are available on
|
|
GCC from version 4.4 and Clang.
|
|
|
|
On other compilers, there's just a test to see if the allocator has
|
|
a member, but no check that it can be called. So rather than using a
|
|
fallback there will just be a compile error.
|
|
|
|
`propagate_on_container_copy_assignment`,
|
|
`propagate_on_container_move_assignment`,
|
|
`propagate_on_container_swap` and
|
|
`select_on_container_copy_construction` are also supported.
|
|
Due to imperfect move emulation, some assignments might check
|
|
`propagate_on_container_copy_assignment` on some compilers and
|
|
`propagate_on_container_move_assignment` on others.
|
|
|
|
=== Construction/Destruction Using Allocators
|
|
|
|
The following support is required for full use of {cpp}11 style
|
|
construction/destruction:
|
|
|
|
* Variadic templates.
|
|
* Piecewise construction of `std::pair`.
|
|
* Either `std::allocator_traits` or expression SFINAE.
|
|
|
|
This is detected using Boost.Config. The macro
|
|
`BOOST_UNORDERED_CXX11_CONSTRUCTION` will be set to 1 if it is found, or 0
|
|
otherwise.
|
|
|
|
When this is the case `allocator_traits::construct` and
|
|
`allocator_traits::destroy` will always be used, apart from when piecewise
|
|
constructing a `std::pair` using `boost::tuple` (see <<compliance_pairs,below>>), but that should be easily avoided.
|
|
|
|
When support is not available `allocator_traits::construct` and
|
|
`allocator_traits::destroy` are never called.
|
|
|
|
=== Pointer Traits
|
|
|
|
`pointer_traits` aren't used. Instead, pointer types are obtained from
|
|
rebound allocators, this can cause problems if the allocator can't be
|
|
used with incomplete types. If `const_pointer` is not defined in the
|
|
allocator, `boost::pointer_to_other<pointer, const value_type>::type`
|
|
is used to obtain a const pointer.
|
|
|
|
=== Pairs
|
|
|
|
Since the containers use `std::pair` they're limited to the version
|
|
from the current standard library. But since {cpp}11 ``std::pair``'s
|
|
`piecewise_construct` based constructor is very useful, `emplace`
|
|
emulates it with a `piecewise_construct` in the `boost::unordered`
|
|
namespace. So for example, the following will work:
|
|
|
|
[source,c++]
|
|
----
|
|
boost::unordered_multimap<std::string, std::complex> x;
|
|
|
|
x.emplace(
|
|
boost::unordered::piecewise_construct,
|
|
boost::make_tuple("key"), boost::make_tuple(1, 2));
|
|
----
|
|
|
|
Older drafts of the standard also supported variadic constructors
|
|
for `std::pair`, where the first argument would be used for the
|
|
first part of the pair, and the remaining for the second part.
|
|
|
|
=== Miscellaneous
|
|
|
|
When swapping, `Pred` and `Hash` are not currently swapped by calling
|
|
`swap`, their copy constructors are used. As a consequence when swapping
|
|
an exception may be thrown from their copy constructor.
|
|
|
|
Variadic constructor arguments for `emplace` are only used when both
|
|
rvalue references and variadic template parameters are available.
|
|
Otherwise `emplace` can only take up to 10 constructors arguments.
|
|
|
|
== Open-addressing Containers
|
|
|
|
The C++ standard does not currently provide any open-addressing container
|
|
specification to adhere to, so `boost::unordered_flat_set`/`unordered_node_set` and
|
|
`boost::unordered_flat_map`/`unordered_node_map` take inspiration from `std::unordered_set` and
|
|
`std::unordered_map`, respectively, and depart from their interface where
|
|
convenient or as dictated by their internal data structure, which is
|
|
radically different from that imposed by the standard (closed addressing).
|
|
|
|
Open-addressing containers provided by Boost.Unordered only work with reasonably
|
|
compliant C++11 (or later) compilers. Language-level features such as move semantics
|
|
and variadic template parameters are then not emulated.
|
|
The containers are fully https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^].
|
|
|
|
The main differences with C++ unordered associative containers are:
|
|
|
|
* In general:
|
|
** `begin()` is not constant-time.
|
|
** `erase(iterator)` does not return an iterator to the following element, but
|
|
a proxy object that converts to that iterator if requested; this avoids
|
|
a potentially costly iterator increment operation when not needed.
|
|
** There is no API for bucket handling (except `bucket_count`).
|
|
** The maximum load factor of the container is managed internally and can't be set by the user. The maximum load,
|
|
exposed through the public function `max_load`, may decrease on erasure under high-load conditions.
|
|
* Flat containers (`boost::unordered_flat_set` and `boost::unordered_flat_map`):
|
|
** `value_type` must be move-constructible.
|
|
** Pointer stability is not kept under rehashing.
|
|
** There is no API for node extraction/insertion.
|
|
|
|
== Concurrent Containers
|
|
|
|
There is currently no specification in the C++ standard for this or any other 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.Unordered concurrent containers are technically not models of
|
|
https://en.cppreference.com/w/cpp/named_req/Container[Container^], although
|
|
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:
|
|
|
|
* Access to an element previously located via lookup.
|
|
* Container traversal.
|
|
|
|
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:
|
|
|
|
[source,c++]
|
|
----
|
|
iterator find(const key_type& k);
|
|
std::pair<iterator, bool> insert(const value_type& obj);
|
|
----
|
|
|
|
are transformed to accept a _visitation function_ that is passed such element:
|
|
|
|
[source,c++]
|
|
----
|
|
template<class F> size_t visit(const key_type& k, F f);
|
|
template<class F> bool insert_or_visit(const value_type& obj, F f);
|
|
----
|
|
|
|
(In the second case `f` is only invoked if there's an equivalent element
|
|
to `obj` in the table, not if insertion is successful). Container traversal
|
|
is served by:
|
|
|
|
[source,c++]
|
|
----
|
|
template<class F> size_t visit_all(F f);
|
|
----
|
|
|
|
of which there are parallelized versions in C++17 compilers with parallel
|
|
algorithm support. In general, the interface of 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); In the case of `boost::concurrent_flat_set`, visitation is always const.
|
|
|
|
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`].
|
|
|
|
//-
|