diff --git a/doc/container.qbk b/doc/container.qbk index f612303..2bb4b70 100644 --- a/doc/container.qbk +++ b/doc/container.qbk @@ -413,8 +413,8 @@ its erase functions (AssocVector::erase invalidates all iterators into the objec complexity guarantees of insert and erase (linear as opposed to constant). ]] [*Boost.Container] [classref boost::container::flat_map flat_map], [classref boost::container::flat_set flat_set], [classref boost::container::flat_multimap flat_multimap] and [classref boost::container::flat_multiset flat_multiset] containers are ordered, vector-like container based, associative -containers following Austern's and Alexandrescu's guidelines. These ordered vector containers have also -benefited with the addition of `move semantics` to C++11, speeding up insertion and +containers following Austern's and Alexandrescu's guidelines. These ordered vector-like containers have also +benefited with the addition of `move semantics`, speeding up insertion and erasure times considerably. Flat associative containers have the following attributes: * Faster lookup than standard associative containers @@ -428,6 +428,44 @@ erasure times considerably. Flat associative containers have the following attri (copy/move constructors can throw when shifting values in erasures and insertions) * Slower insertion and erasure than standard associative containers (specially for non-movable types) +[*Differences with the standard `std::flat_map`/`std::flat_set`]. C++23 added `std::flat_map`, `std::flat_set`, +`std::flat_multimap` and `std::flat_multiset`, based on the same sorted-vector idea. There are, however, several +notable differences with [*Boost.Container]'s long-standing implementation (available since 2004 and usable from +C++03 onwards): + +* [*Container vs. container adaptor]: [*Boost.Container]'s flat containers are full-fledged containers that own a + single underlying sorted sequence. The standard ones are container [*adaptors] layered on top of user-provided + sequence containers, exposed through `keys()`/`values()` accessors and `extract()`/`replace()` operations. + +* [*Storage layout for maps] (array of structs vs. structure of arrays): + [classref boost::container::flat_map flat_map] stores its elements as a single sequence of `value_type` = + `std::pair`, so each key is interleaved with its mapped value in one contiguous buffer. `std::flat_map` + instead keeps keys and mapped values in two [*separate] parallel containers (`key_container_type` and + `mapped_container_type`). The standard layout can speed up key-only scans (lookups touch only the keys array) at + the cost of an extra indirection when both key and value are needed, while Boost's layout keeps each key next to + its value. + +* [*Iterators and `value_type`]: because [classref boost::container::flat_map flat_map] holds real + `std::pair` objects, its iterators dereference to actual pair lvalues and `&*it` yields a pointer to a + stored pair. `std::flat_map`, having two arrays, exposes proxy references of type + `pair` and does not provide pointers into a single pair array. + +* [*Underlying sequence access]: [*Boost.Container] exposes the whole sorted vector through `sequence_type`, + `extract_sequence()` and `adopt_sequence()` (optionally with the `ordered_unique_range_t` tag for an O(1) + adoption), which is handy to build the container cheaply and then re-adopt it. The standard counterpart uses + `extract()`/`replace()` returning the key and mapped containers separately. + +* [*Availability and configurability]: [*Boost.Container]'s flat containers work from C++03, let you choose the + underlying vector-like sequence (e.g. [classref boost::container::small_vector small_vector] or + [classref boost::container::static_vector static_vector]) through a template parameter and offer additional + ordered-range insertion overloads. + +The following example shows the most common operations, the single-sequence storage and the +`extract_sequence`/`adopt_sequence` idiom: + +[import ../example/doc_flat_map.cpp] +[doc_flat_map] + [endsect] [section:devector ['devector]] diff --git a/example/doc_flat_map.cpp b/example/doc_flat_map.cpp new file mode 100644 index 0000000..d6db5df --- /dev/null +++ b/example/doc_flat_map.cpp @@ -0,0 +1,69 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2024-2024. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +//[doc_flat_map +#include +#include //boost::move + +//Make sure assertions are active +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +int main () +{ + using namespace boost::container; + + typedef flat_map map_t; + + map_t m; + + //Like vector, we can reserve storage to avoid reallocations while filling. + m.reserve(8); + + //Insertions keep the underlying vector sorted by key. They are O(N) + //because the elements after the insertion point must be shifted. + m[30] = 3; + m[10] = 1; + m[20] = 2; + assert(m.size() == 3); + + //Iteration is in key order and over contiguous memory, using random-access + //iterators, so it is much faster than a node-based std::map. + { + map_t::const_iterator it = m.begin(); + assert(it->first == 10 && it->second == 1); ++it; + assert(it->first == 20 && it->second == 2); ++it; + assert(it->first == 30 && it->second == 3); + } + + //Lookup uses binary search: O(log N). + map_t::iterator f = m.find(20); + assert(f != m.end() && f->second == 2); + + //All values live in a single underlying sequence of value_type, which is + //std::pair (an array of structs). This is a key difference with the + //C++23 std::flat_map, a container *adaptor* that keeps keys and mapped + //values in two separate, parallel containers (a structure of arrays). + const map_t::value_type *raw = &*m.begin(); + assert(raw[0].first == 10 && raw[2].first == 30); + + //The underlying sorted vector can be moved out with extract_sequence() and + //moved back in with adopt_sequence(). Because the extracted sequence is + //already ordered and free of duplicates, we can re-adopt it in O(1) using + //the ordered_unique_range_t overload. + map_t::sequence_type seq = m.extract_sequence(); + assert(m.empty()); + m.adopt_sequence(ordered_unique_range_t(), boost::move(seq)); + assert(m.size() == 3 && m.find(30) != m.end()); + + return 0; +} +//]