C++ · Containers

Containers

#include <smashtable/atomic_hash_table.hpp>#include <smashtable/atomic_intrusive_stack.hpp>#include <smashtable/basic_avl_tree.hpp>#include <smashtable/basic_fixed_pool.hpp>#include <smashtable/basic_flat_set.hpp>#include <smashtable/basic_growing_pool.hpp>#include <smashtable/basic_hash_index.hpp>#include <smashtable/basic_hash_table.hpp>#include <smashtable/basic_ring.hpp>#include <smashtable/basic_vector.hpp>#include <smashtable/basic_wb_tree.hpp>#include <smashtable/hash_layout.hpp>#include <smashtable/immutable_b_tree.hpp>#include <smashtable/immutable_elias_fano.hpp>#include <smashtable/immutable_packed_array.hpp>#include <smashtable/immutable_splus_tree.hpp>#include <smashtable/shared.hpp>#include <smashtable/intrusive_list.hpp>#include <smashtable/row_search.hpp>#include <smashtable/partitioned_store.hpp> 1.0.0-rc.1 · c4d34add

Terminology first, since both words are overloaded:

  • “Concurrency” does not imply threads. Several transactions can be open on one thread.
  • “Consistency” does not imply strict serializability. Weaker levels exist, and each store here names the one it targets as a compile-time isolation_k rather than leaving you to infer it.

Headers group as:

  basic_*               → Cores. Own their memory, no transactions, one thread.
    ├─ basic_vector<T, Alloc>
    ├─ basic_avl_tree<T, Comparator, Alloc>
    ├─ basic_wb_tree<T, Comparator, Alloc>          # also `rank` and `select`
    ├─ basic_hash_table<T, Hash, Equals, Alloc>     # grows, iterates, rehashes
    ├─ basic_flat_set<T, Comparator, Kit, Alloc>    # sorted, one allocation, searched a row at a time
    ├─ basic_ring<T, Alloc>                         # first-in first-out, capacity fixed at `make`
    ├─ basic_hash_index<T, Index, Hash, Alloc, Kit> # keys to record positions, capacity fixed, no tombstones
    ├─ basic_fixed_pool<T, Index, Alloc>             # records taken and vacated by index, handles that go stale
    ├─ basic_growing_pool<T, Index, Alloc>     # the same in chunks that never move, read beside its growth
    └─ intrusive_list<Index>                        # first-in first-out through the records' own `next`

  Immutable, built once and never written
    ├─ immutable_b_tree<T, KeysPerRow, Kit, Alloc>     # keys on every level
    ├─ immutable_splus_tree<T, KeysPerRow, Kit, Alloc> # every key in the leaves, separators above
    ├─ immutable_elias_fano<T, Alloc>                  # non-decreasing integers in a few bits apiece
    └─ immutable_packed_array<T, Alloc>                # integers as distances from a base, one width each

  atomic_*              → Pinned core. Fixed capacity, atomic per slot, callback reads.
    └─ atomic_hash_table<T, Hash, Equals, Alloc>    # the one type that runs on a GPU
       ↑ ::adopt(std::move(growable).release())     ↓ ::adopt(std::move(pinned).release())

  atomic_intrusive_stack<T, Ref>  → any thread pushes, one takes all, no capacity

  *_store               → 2-phase commit, watch and CAS.
    ├─ monotonic_store<Core>                        # one visible version per key
    ├─ snapshot_store<Core>                         # every version a live reader can still name
    └─ reference_store<T, Comparator, Alloc>        # the same contract over `std::set`, kept as the oracle
       ↓ either of the first two wraps a core; either of these wraps a store
    ├─ locked_store<Store, Mutex>                   # one lock spans a whole commit
    └─ partitioned_store<Store, Hash, Mutex, N>     # one lock per partition, one shared commit order

  transaction_group<Stores...>  → One 2-phase commit spanning several stores

§ Choosing a Container for a Concurrency Shape

The Writers column of the table in What’s Inside names how many threads may write, and the reader rule follows from it rather than standing beside it: a core’s const methods are safe from any number of threads only while no thread is writing, since a probe reads slot metadata and node links a write is midway through changing. The shape that gets this wrong most often is a cache behind a mutable member, filled on what its caller calls a read — a reader-writer lock held in shared mode admits several such calls at once, and each of them is a writer as far as the container is concerned.

Growth is the other half of the rule. A core that can grow repoints the regions every live slot reference and every node pointer has already cached, so a container a second thread can reach must either be pinned at a capacity nobody exceeds, or reached through a wrapper that excludes. basic_growing_pool is the one core whose growth repoints nothing: it adds chunks and never moves a record, so readers by index or handle need no claim while its one writer takes, vacates and grows.

Reach forWrap in
One thread, or many readers and no writerbasic_vector · basic_ring · basic_flat_set · basic_fixed_pool…
The same, and orderedbasic_avl_tree · basic_wb_tree…
The same, and keyedbasic_hash_table · basic_hash_index…
Built once from sorted keys, read foreverimmutable_b_tree · immutable_splus_tree…
Built once from sorted integers, compressedimmutable_elias_fano…
Built once from integers in a narrow bandimmutable_packed_array…
One writer growing, readers by indexbasic_growing_pool…
Many threads, capacity fixed up frontatomic_hash_table…
Many producers, one consumer draining wholeatomic_intrusive_stack…
Many threads, and the container still growsany core abovelocked_store · partitioned_store
Versions a reader can still namemonotonic_store · snapshot_store · reference_storeeither wrapper
One commit spanning several storestransaction_groupinherits its participants’

Both wrappers take their mutex as a template parameter, and that mutex takes two of its own: the reference its word is owned through, and the policy a waiting core follows. The pinned table takes the same two directly, since its slot lock is the mutex it has. A deployment that knows its hardware names both: a reference whose conditional add decides an acquire in one instruction, and a stall its cores actually have. One that does not pays nothing for the question.

Transactions and thread-safety are two independent axes, not one ladder. A wrapper takes its mutex per call, not across a transaction: stage and commit each take it and drop it in between, and only a group holds it from its validation to its publication. What carries the inner store’s level across that gap is the reservation staging made, since no other transaction can claim those keys until this one publishes or unwinds. A store in no order gives its reader no stamp to hold, so a monotonic reader spanning partitions can catch a commit half-applied and only Read Committed survives — but a snapshot reader answers at its own stamp, and every partition belongs to one order, so it keeps Snapshot across all sixteen. Every container publishes what it promises as isolation_k, so the level is checkable rather than folklore. atomic_hash_table has no transactions at all — it offers per-operation atomicity, which is a different product, and is why it is not a *_store.

status_t reports out_of_memory_heap_k == ENOMEM, invalid_argument_k == EINVAL, key_not_found_k == ENOENT and others, and is [[nodiscard]] on every mutating API.

§ Making std::set Transactional

smashtable/reference_store.hpp

The baseline reference design, and the yardstick the tree containers are held against — it runs the same test suites they do. Updates group into transactions that stage and roll back before committing, which is what lets inter-dependent updates span several stores.

Reads are consistent at the Monotonic Atomic View level, so a transaction never sees another’s partial update. That is weaker than Snapshot Isolation: there is no guarantee that every read within one transaction sees the same snapshot. It keeps a version per key all the same, dated by a generation rather than by a shared commit stamp, which is why a watch is re-resolved at commit instead of compared against one. snapshot_store is the sibling that does make that guarantee, and the section below states what it costs.

§ Adelson-Velsky and Landis Trees

smashtable/basic_avl_tree.hpp

Rarely called by its full name, the AVL tree is the simplest clean self-balancing binary search tree, from 1962. basic_avl_tree<value, comparator, allocator> is an ordered store in the shape of std::set or std::map. Standard libraries usually pick Red-Black trees; AVL balances more rigidly, trading slightly slower updates for faster lookups.

Its API differs from the STL where exception-free reporting demands it:

  • insert() returns std::pair<iterator, bool>, as the STL does.
  • erase(iterator) returns erase_result_t { iterator next; status_t status; }.
  • Range insert(first, last) returns status_t.
  • Hint-based insert(hint, value) and emplace_hint() are deleted, since AVL trees gain nothing from a hint.

§ Weight-Balanced Trees

smashtable/basic_wb_tree.hpp

basic_wb_tree balances on subtree sizes rather than heights, using Δ=3 and Γ=2 — the only proven integer solution, per Hirai and Yamamoto. Storing sizes buys order statistics: select(k) finds the k-th smallest and rank(x) finds a key’s position, both in O(log n). That is what pagination, percentiles and quantiles need, and it is the one thing the AVL tree cannot offer.

Expect depth around 1.88 log₂(n) against AVL’s 1.44, in exchange for O(1) amortized rotations per update.

§ Flat Sets & Rings

smashtable/basic_flat_set.hpp · smashtable/basic_ring.hpp

basic_flat_set is an ordered set in one contiguous allocation, growable and allocator-aware like basic_avl_tree, differing only in where elements live. Inserting and erasing shift the tail, so it suits a few hundred elements rather than millions, in exchange for a search that reads adjacent memory and allocates nothing. That search is the same row search the immutable trees below use, wherever the element is a row_searchable_key ordered by less_t, and a plain binary search otherwise — basic_flat_set takes the kit as its third template argument. It answers the same ordered_collection surface the trees do — bounds, ranges and half-open erasure — so the shared suites hold it to the same contract.

basic_ring is a first-in first-out queue over one allocation whose capacity is a power of two fixed at make, for batching and read-ahead. Two 32-bit counters of pushes and pops wrap together, so their difference is the size and a full ring is told from an empty one without a spare slot.

§ Immutable B & S+ Trees

smashtable/immutable_b_tree.hpp · smashtable/immutable_splus_tree.hpp · smashtable/row_layout.hpp · smashtable/row_search.hpp

Both are built once from a sorted span and never written again, so any number of threads may read one at once with no synchronization at all. Both search a row at a time rather than a key at a time — a row being a span wide enough to be worth one vectorized pass, 512 bytes by default, which is default_row_bytes_k. row_layout.hpp says what a key may be and where each one sits in a row; row_search.hpp says how a row is searched.

immutable_b_tree stores nodes breadth-first with implicit children: node i holds one row of B keys and its children are nodes i*(B+1)+j+1. A lookup reads one row per level and the rank adds up as the descent goes, with no subtree sizes stored.

immutable_splus_tree keeps every key in sorted leaves and copies separators onto the levels above. A lookup lands on the leaf whose position is the rank, so select is an index into the leaves.

Every kit the compiler can emit is compiled into the same artifact, each under its own target attributes, so no global instruction-set flag is needed. detect_row_kit() probes the processor once and visit_row_kit hands the answer over as a type, so a tree instantiated over it calls its kit with no table and no branch per key.

visit_row_kit(detect_row_kit(), [&](auto kit) {
    using tree_t = immutable_b_tree<std::uint64_t, 64, decltype(kit)>;
    // …built once, then read from any number of threads
});

Keys may be 32- or 64-bit of either signedness, or 16 bytes ordered as memcmp. A 16-byte key is stored either interleaved or split into a column of high words and a column of low words, the low column read only where a high word ties.

immutable_b_set and immutable_splus_set hold bare keys; immutable_b_map and immutable_splus_map pair each key with a value. A map keeps the rows byte-identical to its set twin and puts the values in one array the rank indexes, so every kit still loads a row of nothing but keys, and mapped_at(rank) costs one further cache line.

§ Elias-Fano Sequences

smashtable/immutable_elias_fano.hpp

immutable_elias_fano keeps non-decreasing integers - the offsets of variable-length rows, say - in at most 2 + ⌈log₂(universe / count)⌉ bits apiece, and select(i) reads the i-th one back in constant time. The low bits of every value are packed side by side and the high bits are stored in unary, with every 256th set bit’s position sampled, so a select jumps to a sample and counts a handful of words. make takes the values whole, or pulls them in order through a callback, so a column of offsets streams in from storage without ever sitting in memory at full width.

std::uint64_t const offsets[] = {0, 3, 3, 10, 42};
auto sequence = immutable_elias_fano<>::make(std::span<std::uint64_t const>(offsets));
std::uint64_t const row_bytes = sequence->select(3) - sequence->select(2); // 7

§ Packed Arrays

smashtable/immutable_packed_array.hpp

immutable_packed_array keeps unsigned integers between a base and a maximum as their distance from the base, in bit_width(maximum - base) bits apiece, and array[i] reads the i-th one back in constant time. Values that all equal the base cost no words at all, so a column of commit stamps coded against the oldest live snapshot is free once nobody reads below it. immutable_elias_fano keeps its low parts in one, based at zero. save writes a three-word header and the packed words, and immutable_packed_array_view reads them in place from borrowed bytes at any alignment, so a memory-mapped file is read without a copy.

std::uint64_t const stamps[] = {1000, 1003, 1001, 1007};
auto packed = immutable_packed_array<>::make(std::span<std::uint64_t const>(stamps)); // base 1000, 3 bits apiece
std::vector<std::byte> bytes(packed->saved_bytes());
_ = packed->save(bytes);
auto view = immutable_packed_array_view<>::make(bytes);
std::uint64_t const third = (*view)[2]; // 1001

§ Transactional Stores

smashtable/monotonic_store.hpp

monotonic_store<Core> adds two-phase commit, watches and CAS on top of any key-addressable core. monotonic_avl_set, monotonic_avl_map, monotonic_wb_set and monotonic_wb_map are the aliases you’ll name directly. monotonic_hash_set and monotonic_hash_map back the same store with the open-addressed table, which supplies no ordering, so bounds, ranges and order statistics are gated out of those instantiations at compile time.

Which version a reader sees is decided by one number, stamped when a transaction commits. A version that has not committed carries no stamp, so it is invisible to readers and cannot make anyone else’s validation fail — a transaction that stages and then rolls back costs its peers nothing. Two writers on one key are ordered by their stamps, and the later one wins. That is a lost update, which this level permits: Monotonic Atomic View constrains what a reader observes, not what two writers do to each other, and a level that is available under partition provably cannot prevent it. A writer that needs the first commit to win must watch() the key, or use a snapshot container, where the write set is validated whether you ask or not.

find never records what it read, here. A read set is memory, and a read that allocates is a read that can fail, so on this engine the recording variant is a different name:

_ = transaction.find(key, on_found, on_missing);             // answers, and records nothing
_ = transaction.find_and_watch(key, on_found, on_missing);   // records, and can report out of memory

Both return status_t, because a read that cannot write down what it read has to say so where it happened rather than poisoning the transaction silently until commit. snapshot_store at serializable_k and above needs no second name: every read records, and every read reports.

An erase leaves a tombstone that readers skip, and vacuum() is what returns that space:

expected<std::size_t> const reclaimed = store.vacuum();          // everything no reader can name
expected<std::size_t> const window = store.vacuum(lower, upper); // ordered cores, one slice at a time

§ Snapshot Isolation

smashtable/snapshot_store.hpp

snapshot_store<Core> is the sibling that fixes a transaction’s reads to one instant, and it is where multi-version concurrency control is at its most literal here: a transaction draws a snapshot when it opens, every read is answered from the newest version at or below that snapshot, and a writer never overwrites what a reader can still name. It rebinds the same cores, exports snapshot_avl_set, _avl_map, _wb_set, _wb_map, _hash_set and _hash_map, and publishes isolation_k == snapshot_k. A repeated read returns what it first saw, a repeated range admits no phantoms, and neither holds in its sibling.

The same template answers at three levels, selected by a second parameter rather than by a second implementation, and each has aliases of its own:

snapshot_store<Core>                        // isolation_k == snapshot_k
serializable_store<Core>                    // reads validated too
strict_serializable_store<Core>             // and a commit waits for its own publication

serializable_avl_map<Key, Value>            // and `_avl_set`, `_wb_*`, `_hash_*`
strict_serializable_avl_map<Key, Value>     // the same six for the top rung

Versions are kept as ordinary entries keyed by (key, generation) rather than chained off one entry, on ordered and unordered cores alike. The hash core still hashes the bare key, so every version of a key shares one probe run and a lookup walks that run instead of stopping at the first match.

Reclamation is the other half of multi-version concurrency control, and the whole of it here: old versions are freed against a low-water mark that sits at the oldest open snapshot, so it can never pass a snapshot somebody still holds, and it advances as soon as the oldest reader leaves rather than waiting for the last. Pruning happens on every commit, and vacuum() reaches the keys nobody writes again. There is no background thread and no epoch registry. clear() refuses while a reader is open rather than dropping versions out from under it.

Range writes publish under one stamp, so a reader on an older snapshot sees all of a range erase or none of it. update_range builds new versions rather than rewriting the visible one, which is what open readers make unsound. select and rank descend on the weight-balanced tree’s augmented count, exact at the newest commit — a reader at an older snapshot gets the merged walk instead, because one number per node cannot answer for an unbounded parameter.

What it costs, on an AVL core over mapping<key, int>:

monotonic_storesnapshot_store
Resident per key, one version80 B72 B
Each retained older version48 B chain node72 B, a full entry
Read of one keyone chain headthe key’s whole run

At rest it is the cheaper of the two, because a key carries no chain-head pointer and no per-entry allocator. It becomes the more expensive one exactly when a long-lived reader pins history — and on the hash core a pinned version occupies a real slot, so it lengthens the probe runs of its neighbours as well. With no transaction open the low-water mark sits at the newest commit and the whole version tail is freed on the next write, so a store nobody is reading costs what its sibling costs.

§ Hash Tables

smashtable/basic_hash_table.hpp

hash_set<key> and hash_map<key, value> are open-addressing containers with linear probing. They differ from google::dense_hash_map and tsl::robin_map by not reserving sentinel key values for tombstones, by unpacking keys and values into disjoint arrays, and by carrying two bits of metadata per slot rather than a byte.

Keys and values live in separate regions, so a lookup that only compares keys never pulls values into cache — which is most of them, since a miss touches no value at all. Thirty-two slots share one 64-bit header holding two parallel bitmasks, encoding free, deleted, populated and locked in two bits each. Sixty-four bits is the widest atomic every target supports, which is what lets a whole bucket header move in one instruction.

Growing repoints the key, value and header regions that every live slot reference has cached, so a table that can grow can never be read concurrently. The type system enforces that rather than a sentence: two types, neither of which includes the other’s header. basic_hash_table grows, iterates and rehashes; atomic_hash_table is pinned and atomic over the slot each operation touches. What passes between them is the allocation itself — a hash_storage — so the hand-off is a move of a value rather than one table reaching into the other:

_ = growable.reserve(1u << 20);                                          // pin the capacity first
auto pinned = atomic_hash_map<key_t, value_t>::adopt(std::move(growable).release());

_ = pinned.find(key, [](auto const &slot) noexcept { use(slot.value()); });
if (failed(pinned.emplace(key, value))) report_full();                   // a pinned table can fill up

auto compacted = hash_map<key_t, value_t>::adopt(std::move(pinned).release());   // iterators return

Every operation on the pinned table is atomic over the slot it touches: it takes that slot by setting both its bits with a fetch_or and releases it by posting the xor of the difference to the desired state, which reads nothing back, so neither path needs a compare-and-swap loop. It is not lock-free, though — fetch_or spins until it wins the slot, so a thread descheduled while holding one blocks every other prober that walks onto it. No global lock and no reallocation is what the pinning buys; a stalled thread stalling its neighbours is what it does not. Reads take a callback rather than returning a reference or an iterator, since both would dangle the moment another thread erased the slot. Tombstones only accumulate while pinned, because compaction needs a rehash — deleted_count() is what says it is time to hand the storage back.

Readers take the slot lock like writers do. A reader that merely loaded the header would be unsound, and measurably so: the two bits per slot cannot distinguish “untouched” from “locked, rewritten and unlocked”, because a completed write cycle restores exactly the bits the reader first saw. Adding a per-bucket version counter would fix it and would cost half the slots per bucket, which is not worth breaking the one-warp-per-bucket geometry for.

Order-dependent operations are absent by construction: there is no range, erase_range, lower_bound or select.

smashtable/basic_hash_index.hpp

basic_hash_index is the same layout serving another job: a fixed-capacity map from 4-, 8- or 16-byte keys to the positions of records kept elsewhere, such as a fixed pool’s. Its keys sit in the keys region, so a probe compares a window of them without loading a record, and a key may repeat, since it is often a hash of something longer: find takes a check that reads the record behind each match, and erase names the exact position. A tombstone would make every later probe walk past the erased slot until a rehash, which a fixed-capacity index never runs, so an erase shifts the rest of its run back over the hole instead. Every key stays reachable from its home, the deletion bits stay zero, and capacity is at most half the slots, so a run always ends at a free one and a table under constant churn probes no further than a fresh one.

API

Types

hash_table_iteratorForward iterator for hash tables using simplified slot-based addressing.
basic_hash_table.hpp:92 ↗
template <typename value_type_,
          typename hasher_type_>
struct hash_table_iterator

Inherits the three region pointers and the slot index from hash_slot_ref, and adds a counter of unvisited slots. Implements std::forward_iterator_tag for sequential traversal.

slots_remaining counts the slots left to visit and reaching zero is the end, which is what is_end and isnt_end report and what the end_sentinel_t comparisons consult. Two iterators compare equal when they name the same slot of the same keys region. advance steps to the next populated slot and must not run at the end, while skip_non_populated walks past free, deleted and locked slots until a populated one or the end is reached.

Fields

slots_remainingoffset_t
no_augmentation_tMarks a tree that keeps only size, leaving nodes byte-identical to an unaugmented build.
basic_wb_tree.hpp:50 ↗
struct no_augmentation_t
no_augmented_count_tStand-in for the augmented counter in an unaugmented tree, occupying no bytes.
basic_wb_tree.hpp:66 ↗
struct no_augmented_count_t
default_allocatorThe allocator every container defaults to, over ::operator new asked not to throw.
shared.hpp:476 ↗
template <typename value_type_>
struct default_allocator

std::allocator would serve, and costs <memory>, some fifty thousand preprocessed lines, in headers that want nothing else from it. Exhaustion is reported by returning null, which is the shape every caller here already checks, rather than by an exception nothing would catch.

less_tTransparent ordering and equality, so a container needs no <functional>.
shared.hpp:527 ↗
struct less_t

Both are heterogeneous - the is_transparent tag is what lets a lookup compare a key against something merely comparable to it, without materializing a key first - which is the only property the containers here relied on std::less and std::equal_to for.

hashThe hash a container uses unless told otherwise.
shared.hpp:552 ↗
template <typename key_type_,
          typename = void>
struct hash

Specialize it for your own key.

Deliberately not a forward to std::hash: that would drag <functional> into every header, and the point of a named trait is that an unhashable key says so at compile time. Hashing the bytes of an arbitrary type instead would be worse than an error - padding, or an equality that ignores some field, would give two equal keys different hashes and quietly lose lookups.

moving_pointerReads a contiguous run as rvalues, so a bulk write moves its staged copies rather than copying them again.
shared.hpp:654 ↗
template <typename value_type_>
struct moving_pointer

Fields

positionvalue_type_ *
reversed_iteratorWalks a bidirectional iterator_type_ backwards, standing one position past the element it reads, so the reversed begin is the forward end.
shared.hpp:673 ↗
template <typename iterator_type_>
class reversed_iterator
end_sentinel_tSentinel type for range-based iteration end conditions.
shared.hpp:727 ↗
struct end_sentinel_t
assume_reserved_tTag to assume capacity is pre-allocated, skip null checks.
shared.hpp:793 ↗
struct assume_reserved_t

Similar to std::adopt_lock for assuming preconditions are met.

See also

assume_uninitialized_tTag to leave new elements unconstructed, for storage a kernel or a byte copy writes first.
shared.hpp:801 ↗
struct assume_uninitialized_t
assume_unique_tTag to assume elements are unique, skip equality comparisons.
shared.hpp:810 ↗
struct assume_unique_t

Similar to Boost.Container's ordered_unique_range_t.

assume_sorted_tTag to assume the input range is ascending, which buys an O(n) bulk build.
shared.hpp:821 ↗
struct assume_sorted_t

Warnings

An unsorted range is undefined behaviour, not a refusal.

descending_tAsks an ordered walk for its members greatest first.
shared.hpp:829 ↗
struct descending_t
unbounded_tStands for the missing upper end of a range, so a walk bounded above runs to the last member.
shared.hpp:837 ↗
struct unbounded_t
incumbent_policy_tWhat an absorbed batch does with a key the destination already holds.
shared.hpp:2456 ↗
enum incumbent_policy_t

Implementation vocabulary rather than a tag: a caller picks between these by naming a verb, and the three verbs share one staging helper that this tells apart.

Values

keeps_the_incumbent_kThe element already stored stays, and the newcomer is dropped.
takes_the_newcomer_kThe newcomer takes the key, overwriting what was stored under it.
refuses_the_newcomer_kNeither is stored: a key arriving over one already there refuses the whole batch.
algebra_tWhich members of the two sides a walk keeps.
shared.hpp:4944 ↗
enum algebra_t

Values

union_k
intersection_k
difference_k
symmetric_difference_k
membership_tWhether the side being walked is the only one holding a member, or both are.
shared.hpp:4952 ↗
enum membership_t

Values

on_one_side_k
on_both_sides_k
ring_eviction_tWhether push_evicting had to drop the oldest element to make room.
basic_ring.hpp:27 ↗
enum ring_eviction_t

Values

kept_every_element_k
evicted_oldest_k
hash_bucket_head_tBucket header containing metadata for hash_bucket_capacity_k slots.
hash_layout.hpp:79 ↗
union hash_bucket_head_t

Two parallel 32-bit bitmasks - population, deletion - give each slot two bits, four states.

Slot State Encoding

Each slot's state is determined by corresponding bits in both masks:

  • 00 (populations=0, deletions=0): Free slot, never used or fully freed
  • 01 (populations=0, deletions=1): Deleted slot, tombstone from lazy deletion
  • 10 (populations=1, deletions=0): Populated slot with valid key-value pair
  • 11 (populations=1, deletions=1): Locked slot, temporarily held for atomic operations

lanes exposes the two 32-bit halves separately - populations has a set bit for every populated or locked slot, deletions one for every deleted or locked slot. u64 views both at once, which is what enables atomic operations on the entire header (2 × 32 bits = 64 bits).

Fields

lanesstruct ashvardanian::smashtable::hash_bucket_head_t::@376061242304273247160011120072252265272354277145
u64std::uint64_t
hash_slot_state_tThe four states a slot can be in, spelled out by the two header lanes.
hash_layout.hpp:102 ↗
enum hash_slot_state_t

Values

free_k0Never used, or fully freed.
deleted_k1Tombstone left behind by a lazy deletion.
populated_k2Holds a live key, and a value when the table is a map.
locked_k3Temporarily held by a thread performing an atomic operation.
hash_slots_count_tScaling schema that computes the required number of slots for a target element count.
hash_layout.hpp:164 ↗
struct hash_slots_count_t

Caps the load factor at 75%, through a 4/3 multiplier, so linear probing cannot degenerate, and rounds every slot count up to a power of two so the modulo is a bitwise AND.

The 75% load factor strikes a balance between memory efficiency and probe length:

  • Too high (>85%): Linear probing degrades into long search chains
  • Too low (<60%): Wastes memory without significant performance gain
  • 75%: Industry standard, keeps average probe length under 2 hops

raw carries the widened count itself. The primary constructor budgets for a target element count and applies the load factor, while from_slots takes the slot count directly, for callers that already sized the storage. is_addressable reports whether the probe masks can address the count, which requires a power of two.

A request too large to round up to a power of two keeps unrepresentable_k, which is not a power of two and therefore not addressable, so the factories refuse it instead of silently handing back the smallest possible table.

Fields

unrepresentable_kstd::size_tA count that no power of two covers, and which is_addressable therefore rejects.
rawstd::size_t

Notes

The minimum slot count equals hash_bucket_capacity_k, at least one full bucket.

hash_layout_forMetadata extraction template that derives type information for hash table elements.
hash_layout.hpp:220 ↗
template <typename value_type_,
          typename hasher_type_>
struct hash_layout_for

Computes element types, sizes and alignment requirements from the element and hasher parameters, and takes the hasher's return type as the offset_t that indexes slots.

Provides three views of a stored element:

  • value_t: mutable reference pair mapping<key const&, mapped&> for iteration.
  • value_const_t: immutable reference pair mapping<key const&, mapped const&> for lookups.
  • value_copy_t: owned pair mapping<key, mapped> for extraction.

Fields

has_values_kbool
bytes_for_keys_kstd::size_t
bytes_for_values_kstd::size_t
bytes_in_bucket_kstd::size_t

See also

default_hash_tDefers to hash for whatever key arrives, rather than fixing one at declaration.
hash_layout.hpp:304 ↗
struct default_hash_t
hash_slot_refSmart reference to a hash table slot, addressing it as base pointers plus a slot index.
hash_layout.hpp:335 ↗
template <typename value_type_,
          typename hasher_type_>
struct hash_slot_ref

Probing advances slot_ by one, so a step costs a single integer increment rather than three pointer recomputations. Sets carry no values region and leave values_ null.

keys_, values_ and headers_ are the bases of the three regions, the first two indexed by the absolute slot_ and the last by the bucket that owns it. The aliases restate the layout's own key, value, element and offset types, plus dereference_t, which is the element view for maps and a key reference for sets.

header_ref and mask_in_bucket resolve the owning header and the single bit inside it; key, key_ref, value and value_ref reach the two payload regions; the is_ and mark_ families read and write the slot's two bits. Locking is what a threaded reference overrides, so here lock, unlock and try_lock collapse to nothing.

Fields

has_values_kbool
hash_window_slots_tThe slots of a window a probe may still match, and whether a free slot ends the probe there.
hash_layout.hpp:536 ↗
struct hash_window_slots_t

Fields

candidateshash_bucket_mask_t
ends_probebool
packed_array_header_tWhat save writes ahead of the packed words, one 64-bit word per field, so a view needs nothing else to read them.
immutable_packed_array.hpp:39 ↗
struct packed_array_header_t

Fields

countstd::uint64_tHow many values the array holds.
basestd::uint64_tThe distance every stored value is measured from.
bitsstd::uint64_tHow many bits each value takes.
splus_tree_header_tWhat save writes ahead of the rows, one 64-bit word per field, so a view refuses a tree of another shape.
immutable_splus_tree.hpp:49 ↗
struct splus_tree_header_t

Fields

countstd::uint64_tHow many keys the tree holds.
keys_per_rowstd::uint64_tHow many keys one row holds.
key_bytesstd::uint64_tHow many bytes one key takes.
mapped_bytesstd::uint64_tHow many bytes one mapped value takes, zero for a set.
row_kit_tThe kits, one enumerator per microarchitecture, newest instruction sets after the baseline.
row_search.hpp:46 ↗
enum row_kit_t

Values

serial_k
haswell_k
skylake_k
icelake_k
neon_k
sve_k
rvv_k
v128_k
haswell_row_kit_tAVX2 over four 64-bit or eight 32-bit keys per load, unsigned order by flipping the sign bit.
row_search.hpp:278 ↗
struct haswell_row_kit_t

Fields

kit_krow_kit_t
compiled_kbool
mask_equal_width_kstd::size_t
skylake_row_kit_tAVX-512 over eight 64-bit or sixteen 32-bit keys per load, masked loads over a scalar tail.
row_search.hpp:489 ↗
struct skylake_row_kit_t

Fields

kit_krow_kit_t
compiled_kbool
mask_equal_width_kstd::size_t
icelake_row_kit_tThe Skylake kit plus VPOPCNTQ, counting eight words of a bit vector per instruction, and a PDEP every such core runs in hardware, unlike the microcoded one of the Zen 2 parts the Haswell kit also serves.
row_search.hpp:664 ↗
struct icelake_row_kit_t

Fields

neon_row_kit_tAdvanced SIMD over two 64-bit or four 32-bit keys per load, deinterleaving keys as it loads.
row_search.hpp:737 ↗
struct neon_row_kit_t

Fields

kit_krow_kit_t
compiled_kbool
mask_equal_width_kstd::size_t
sve_row_kit_tScalable vectors sized to whatever the processor offers, a loop predicate over a scalar tail.
row_search.hpp:943 ↗
struct sve_row_kit_t

Fields

kit_krow_kit_t
compiled_kbool
mask_equal_width_kstd::size_t
rvv_row_kit_tRISC-V vectors at whatever length the processor grants per step, with no tail.
row_search.hpp:1084 ↗
struct rvv_row_kit_t

Fields

kit_krow_kit_t
compiled_kbool
mask_equal_width_kstd::size_t
v128_row_kit_tWebAssembly SIMD over two 64-bit or four 32-bit keys per load; unsigned 64-bit order by flipping the sign bit, since the instruction set compares 64-bit lanes only as signed.
row_search.hpp:1240 ↗
struct v128_row_kit_t

Fields

kit_krow_kit_t
compiled_kbool
mask_equal_width_kstd::size_t
Classatomic_hash_tableA fixed-capacity open-addressed table whose operations are atomic over the slot they touch, through per-slot spin locks, and over nothing wider.20
atomic_hash_table.hpp:96 ↗
template <typename value_type_,
          typename hasher_type_ = default_hash_t,
          typename equals_type_ = equal_to_t,
          allocator_of<std::byte> allocator_type_ = default_allocator_t,
          waiting_policy<std::uint64_t const *, std::uint64_t> waiting_policy_type_ = bare_waiting_policy_t,
          template<typename> class atomic_reference_ = atomic_ref>
class atomic_hash_table

Built by adopting an allocation someone else sized, which is what guarantees the capacity was reserved before any thread could observe the table.

Warnings

Not lock-free. A slot is held between lock and unlock, so a thread stopped in between blocks every other prober that reaches that slot; see the file header.

value_tEvery stored element: the key itself for a set, a mapping of both halves for a map.
atomic_hash_table.hpp:102 ↗
using value_t = typename layout_t::value_t;
key_tThe key half, which is the whole element for a set.
atomic_hash_table.hpp:105 ↗
using key_t = typename layout_t::key_t;
mapped_tThe mapped half, void for a set.
atomic_hash_table.hpp:108 ↗
using mapped_t = typename layout_t::mapped_t;
key_typeSTL-compatibility definitions, narrowed to what a pinned table can honestly provide.
atomic_hash_table.hpp:136 ↗
using key_type = key_t;
is_associativeTraits the transactional adapters dispatch on.
atomic_hash_table.hpp:147 ↗
using is_associative = std::bool_constant<has_values_k>;
atomic_hash_table
atomic_hash_table.hpp:161 ↗
atomic_hash_table(hasher_t hasher = {}, equals_t equals = {}) noexcept;
atomic_hash_table(atomic_hash_table && other) noexcept;
atomic_hash_table(atomic_hash_table const &)=delete;
operator=
atomic_hash_table.hpp:165 ↗
atomic_hash_table & operator=(atomic_hash_table && other) noexcept;
atomic_hash_table & operator=(atomic_hash_table const &)=delete;
*× 2Surrenders the allocation, leaving this table empty.
atomic_hash_table.hpp:173 ↗
storage_t release() &&noexcept;
swapA cheap copy-less exchange of the allocation and the two functors.
atomic_hash_table.hpp:184 ↗
void swap(atomic_hash_table & other) noexcept;
sizeLive elements.
atomic_hash_table.hpp:194 ↗
offset_t size() const noexcept;

Read atomically but relaxed, so it may lag a concurrent writer - the count is a statistic here, and other threads are moving it while this returns.

capacitySlots this table will never exceed, since it cannot grow.
atomic_hash_table.hpp:198 ↗
offset_t capacity() const noexcept;
deleted_countTombstones left by erase, which only ever grow while the table is pinned.
atomic_hash_table.hpp:203 ↗
offset_t deleted_count() const noexcept;

Compaction needs a rehash, so this is the signal to hand the storage to a growable table.

findInvokes one of the two callbacks, the found one under the matching slot's lock.
atomic_hash_table.hpp:217 ↗
template <typename comparable_key_type_, typename callback_found_type_, typename callback_missing_type_ = no_op_t>
status_t find(
    comparable_key_type_ && wanted,
    callback_found_type_ && callback_found,
    callback_missing_type_ && callback_missing = {}) const noexcept;

Reads arrive through a callback rather than a reference, since a concurrent erase would invalidate anything handed back.

containsReports whether a key equivalent to wanted is present.
atomic_hash_table.hpp:232 ↗
template <typename comparable_key_type_>
expected<bool> contains(comparable_key_type_ && wanted) const noexcept;

Parameters

wantedinKey to probe for.

Returns

Whether the key is present. The probe takes each slot's lock and allocates nothing, so it never refuses.

atDeleted: a reference into a slot is invalid the moment another thread erases it.
atomic_hash_table.hpp:238 ↗
template <typename comparable_key_type_ = key_t const &>
void at(comparable_key_type_ &&)=delete;
beginDeleted: a snapshot of a table other threads are mutating is not a snapshot.
atomic_hash_table.hpp:241 ↗
void begin()=delete;
emplaceInserts, or overwrites the value when the key is already present.
atomic_hash_table.hpp:256 ↗
template <typename convertible_key_type_, typename convertible_mapped_type_>
status_t emplace(
    convertible_key_type_ && key,
    convertible_mapped_type_ && value) noexcept;
template <typename convertible_key_type_>
status_t emplace(convertible_key_type_ && key) noexcept;

Inserts the key, doing nothing when an equal one is already present.

Statuses

  • capacity_exhausted_k No slot along the probe sequence was free. A pinned table can genuinely fill up, so this reports rather than asserts.
updateOverwrites the value of an existing key, doing nothing when it is absent.
atomic_hash_table.hpp:294 ↗
template <typename comparable_key_type_, typename convertible_mapped_type_>
status_t update(
    comparable_key_type_ && key,
    convertible_mapped_type_ && new_value) noexcept;

Notes

Contention has no status of its own: a taken slot is waited on rather than refused.

Statuses

eraseLeaves a tombstone rather than freeing the slot, so probe sequences stay intact.
atomic_hash_table.hpp:311 ↗
template <typename comparable_key_type_>
status_t erase(comparable_key_type_ && key) noexcept;

Notes

Contention has no status of its own: a taken slot is waited on rather than refused.

Statuses

*× 2Takes ownership of an allocation another table built.
atomic_hash_table.hpp:176 ↗
atomic_hash_table adopt(
    storage_t && storage,
    hasher_t hasher = {},
    equals_t equals = {}) noexcept;
Classatomic_intrusive_stackThe newest of a stack of records linked newest first, pushed from any thread and taken whole by one.3
atomic_intrusive_stack.hpp:34 ↗
template <pointer_linked record_type_,
          template<typename> class atomic_reference_ = atomic_ref>
class atomic_intrusive_stack
pushPushes record, which no other thread may reach until it is taken.
atomic_intrusive_stack.hpp:43 ↗
void push(record_t & record) noexcept;
push_chainPushes the records linked from newest down to oldest, whose next is overwritten, in one step, so the taker meets them together and in that order.
atomic_intrusive_stack.hpp:47 ↗
void push_chain(record_t & newest, record_t & oldest) noexcept;
take_allTakes every record pushed so far, the newest first and linked through next, or null.
atomic_intrusive_stack.hpp:56 ↗
record_t * take_all() noexcept;
Classbasic_avl_nodeNon-owning tree node structure for the AVL-Tree implementation, unaware of memory management.24
basic_avl_tree.hpp:120 ↗
template <typename value_type_,
          typename comparator_type_>
class basic_avl_node

Provides static methods for all tree operations: search, insertion, deletion, and traversal. All operations are exception-free and noexcept.

Implementation Notes

AVL trees are self-balancing binary search trees where the height difference between left and right subtrees is at most 1. This "node" class implements the core tree logic including rotations and rebalancing, but doesn't participate in memory management or promise anything all-or-nothing - basic_avl_tree does both.

Features:

  • Never throws exceptions, even on allocation failure
  • Implements lower_bound and upper_bound for iterator-free navigation
  • Supports random sampling within ranges for statistical operations
  • All methods are static and work on raw node pointers for flexibility

Layout: payload is the stored entry, left, right and parent the links, and height the longest downward path to a leaf. The root carries the biggest height in the tree, a non-NULL node has at least one, and zero occurs only in the uninitialized detached state - which makes 1 << height an upper bound on the branch size.

node_placement_tWhat a walk that may create a node did with the key it was handed.
basic_avl_tree.hpp:427 ↗
enum node_placement_t

Values

refused_kNo node was available to store the key, so nothing was stored.
matched_kAn equal key was already there, and match names the node holding it.
made_kA fresh node took the key, and match names it.
is_balancedWhether node's children differ in height by one at most, and link back to it.
basic_avl_tree.hpp:144 ↗
bool is_balanced(node_t *node) noexcept;
find_successorFinds the next node in in-order traversal (successor).
basic_avl_tree.hpp:192 ↗
node_t * find_successor(node_t *node) noexcept;

Parameters

nodeinThe node to find the successor for.

Returns

The successor node, or nullptr if the node is the maximum element.

find_predecessorFinds the previous node in in-order traversal (predecessor).
basic_avl_tree.hpp:213 ↗
node_t * find_predecessor(node_t *node) noexcept;

Parameters

nodeinThe node to find the predecessor for.

Returns

The predecessor node, or nullptr if the node is the minimum element.

lowest_common_ancestorSearches for the lowest common ancestor of two keys.
basic_avl_tree.hpp:238 ↗
template <typename comparable_a_type_, typename comparable_b_type_>
node_t * lowest_common_ancestor(
    node_t *node,
    comparable_a_type_ && a,
    comparable_b_type_ && b,
    comparator_t const & comparator) noexcept;

Parameters

nodeinThe root of the subtree to search.
ainThe first key.
binThe second key.
comparatorinThe comparator instance (may be stateful).

Returns

A pointer to the lowest common ancestor, or nullptr if the tree is empty.

rangeIterates over a range of entries.
basic_avl_tree.hpp:268 ↗
template <typename lower_type_, typename upper_type_, typename callback_type_>
walk_control_t range(
    node_t *node,
    lower_type_ && low,
    upper_type_ && high,
    comparator_t const & comparator,
    callback_type_ && callback) noexcept;

Parameters

nodeinThe root of the subtree to search.
lowinThe lower bound of the range, included.
highinThe upper bound of the range, excluded.
comparatorinThe comparator instance (may be stateful).
callbackinThe function to call for each node in the range.

Returns

Whether the range ran out or a halting callback stopped the walk first.

equal_rangeLocates the nodes bounding all entries equal to comparable.
basic_avl_tree.hpp:283 ↗
template <typename comparable_type_>
node_interval_t equal_range(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Returns

Half-open bounds plus the lowest ancestor, all nullptr on an empty tree.

sampleRandom samples a single node using reservoir sampling for uniform distribution.
basic_avl_tree.hpp:302 ↗
template <typename generator_type_>
requires uniform_random_bits<std::remove_cvref_t<generator_type_>>
node_t * sample(node_t *node, generator_type_ && generator) noexcept;

Parameters

nodeinRoot of subtree.
generatorinoutAny STL-compatible random number generator.

Returns

Pointer to randomly selected node, or nullptr if tree is empty.

Notes

Uses single-pass reservoir sampling for O(n) time with true uniform distribution.

sample_rangeRandom samples nodes within a given range of keys using reservoir sampling.
basic_avl_tree.hpp:330 ↗
template <typename generator_type_, typename lower_type_, typename upper_type_, typename predicate_type_>
requires uniform_random_bits<std::remove_cvref_t<generator_type_>> && std::predicate<predicate_type_ &, node_t *>
node_t * sample_range(
    node_t *node,
    lower_type_ && low,
    upper_type_ && high,
    comparator_t const & comparator,
    generator_type_ && generator,
    predicate_type_ && predicate) noexcept;
template <typename generator_type_, typename lower_type_, typename upper_type_, typename predicate_type_, indexed_writable_iterator<value_t> output_iterator_type_>
requires uniform_random_bits<std::remove_cvref_t<generator_type_>> && std::predicate<predicate_type_ &, node_t *>
void sample_range(
    node_t *node,
    lower_type_ && low,
    upper_type_ && high,
    comparator_t const & comparator,
    generator_type_ && generator,
    predicate_type_ && predicate,
    std::size_t & seen,
    std::size_t reservoir_capacity,
    output_iterator_type_ && reservoir) noexcept;

Multi-sample reservoir sampling for k uniform random nodes in range.

Parameters

nodeinRoot of subtree.
lowinLower bound.
highinUpper bound.
comparatorinComparator instance (may be stateful).
generatorinoutAny STL-compatible random number generator.
predicateinPredicate to filter nodes.
seeninoutCount of matching nodes processed (can span multiple calls).
reservoir_capacityinMaximum number of samples to collect.
reservoiroutBuffer the samples land in, written by index.

Returns

NULL if nothing was found.

Notes

  • Uses single-pass reservoir sampling for O(n) time with uniform distribution.
  • Uses Algorithm R (reservoir sampling) for uniform k-sample in single pass.
build_from_sortedBuilds a balanced AVL tree from sorted range in O(n) time.
basic_avl_tree.hpp:558 ↗
template <multipass_iterator iterator_type_, allocator_of<node_t> node_allocator_type_>
build_result_t build_from_sorted(
    iterator_type_ first,
    std::size_t count,
    node_allocator_type_ & allocator) noexcept;

Precondition: Range [first, first+count) must be sorted according to comparator.

Parameters

firstinIterator to beginning of sorted range.
countinNumber of elements in range.
allocatorinoutHands back a raw node, or nullptr when it cannot.

Returns

What was built.

Notes

  • Complexity: O(n) time, O(log n) recursion depth. Builds perfectly balanced tree by recursively picking middle element as root.
  • A range handing out rvalues, as a move iterator does, gives its elements up.

Warnings

If precondition violated (unsorted input), resulting tree has undefined structure.

Statuses

  • Any status an element's copy returns passes through unchanged.
extractPops the root replacing it with one of descendants, if present.
basic_avl_tree.hpp:640 ↗
extract_result_t extract(
    node_t *node,
    comparator_t const & comparator) noexcept;
template <typename comparable_type_>
extract_result_t extract(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Searches for a matching ancestor and extracts it out.

Parameters

nodeinNode to extract.
comparatorinComparator for element comparison.
comparableinAny key comparable with stored entries.
join_with_rootJoins two trees with a root node between them.
basic_avl_tree.hpp:768 ↗
node_t * join_with_root(
    node_t *left,
    node_t *root_node,
    node_t *right,
    comparator_t const & comparator) noexcept;

Precondition: All elements in left < root < all elements in right. Maintains AVL balance property.

Parameters

leftinLeft subtree (all elements < root).
root_nodeinRoot node to insert between subtrees.
rightinRight subtree (all elements > root).
comparatorinComparator for element comparison.

Returns

The root of the joined tree.

joinJoins two AVL trees into one.
basic_avl_tree.hpp:815 ↗
node_t * join(
    node_t *left,
    node_t *right,
    comparator_t const & comparator) noexcept;

Precondition: All elements in left < all elements in right. Maintains AVL balance property.

Parameters

leftinLeft tree (smaller elements).
rightinRight tree (larger elements).
comparatorinComparator for element comparison.

Returns

The root of the joined tree.

splitSplits an AVL tree at a key: elements less than it go to the left tree, >= to the right tree, and both keep the AVL balance property.
basic_avl_tree.hpp:855 ↗
template <typename comparable_type_>
split_result_t split(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Parameters

nodeinRoot of the tree to split.
comparableinKey to split at.
comparatorinComparator for element comparison.

Returns

Contains left tree (< key) and right tree (>= key).

merge_split_basedMerges two AVL trees using split-based divide-and-conquer.
basic_avl_tree.hpp:893 ↗
node_t * merge_split_based(
    node_t *small,
    node_t *large,
    comparator_t const & comparator) noexcept;

Optimal for unbalanced sizes: O(m log(n/m + 1)) where m <= n. Takes root of smaller tree, splits larger tree around it, recursively merges.

Parameters

smallinSmaller tree to merge (will be consumed).
largeinLarger tree to merge into (will be consumed).
comparatorinComparator for element comparison.

Returns

The root of the merged tree, holding every node from both inputs.

Notes

Both input trees are consumed, ownership transferred, balance kept throughout.

See also

Blelloch et al., "Just Join for Parallel Ordered Sets", 2016

tree_to_spineConverts AVL tree to right-leaning spine (degenerate vine).
basic_avl_tree.hpp:923 ↗
node_t * tree_to_spine(node_t *root, std::size_t & count) noexcept;

A spine is a sorted linked list using right pointers, all left pointers null.

Parameters

rootinTree to convert.
countoutNumber of nodes in resulting spine.

Returns

The head of the spine, which is the smallest element.

Notes

Uses rotations to flatten tree: O(n) time, O(1) space.

merge_spinesMerges two sorted spines into one sorted spine.
basic_avl_tree.hpp:968 ↗
node_t * merge_spines(
    node_t *first_spine,
    node_t *second_spine,
    comparator_t const & comparator) noexcept;

Parameters

first_spineinFirst sorted spine.
second_spineinSecond sorted spine.
comparatorinComparator for element comparison.

Returns

The head of the merged spine.

Notes

O(n+m) time, O(1) space. Just like merging sorted linked lists.

spine_to_balancedConverts sorted spine back to balanced AVL tree (Day-Stout-Warren algorithm).
basic_avl_tree.hpp:1009 ↗
node_t * spine_to_balanced(node_t *spine, std::size_t count) noexcept;

Parameters

spineinHead of spine.
countinNumber of nodes in spine.

Returns

The root of the balanced tree.

Notes

O(n) time using rotations to build perfectly balanced tree.

See also

Stout & Warren, "Tree Rebalancing in Optimal Time and Space", 1986

merge_dswMerges two AVL trees using Day-Stout-Warren spine algorithm.
basic_avl_tree.hpp:1063 ↗
node_t * merge_dsw(
    node_t *first_tree,
    node_t *second_tree,
    comparator_t const & comparator,
    std::size_t & out_size) noexcept;

Best for large similarly-sized trees: O(n+m) time, O(1) space. Flattens both trees to spines, merges spines, rebuilds balanced tree.

Parameters

first_treeinFirst tree to merge (will be consumed).
second_treeinSecond tree to merge (will be consumed).
comparatorinComparator for element comparison.
out_sizeoutTotal number of nodes in result.

Returns

The root of the merged, rebalanced tree.

Notes

High constant factor due to many rotations, but O(1) space. Both input trees are consumed, ownership transferred.

See also

*× 3The node of the subtree at node holding comparable, or nullptr.
basic_avl_tree.hpp:127 ↗
node_t * find(
    node_t *node,
    comparable_type_ const & comparable,
    comparator_t const & comparator) noexcept;
Symbolnodecomparator
findnode_tcomparator_t &
findnode_tcomparator_t &
findnode_type_comparator_type_ &
lower_*_bound× 3The first node of the subtree at node not ordered below comparable, or nullptr.
basic_avl_tree.hpp:128 ↗
node_t * lower__bound(
    node_t *node,
    comparable_type_ const & comparable,
    comparator_t const & comparator) noexcept;
Symbolnodecomparator
lower_boundnode_tcomparator_t &
lower_boundnode_tcomparator_t &
lower_boundnode_type_comparator_type_ &
upper_*_bound× 3The first node of the subtree at node ordered above comparable, or nullptr.
basic_avl_tree.hpp:129 ↗
node_t * upper__bound(
    node_t *node,
    comparable_type_ const & comparable,
    comparator_t const & comparator) noexcept;
Symbolnodecomparator
upper_boundnode_tcomparator_t &
upper_boundnode_tcomparator_t &
upper_boundnode_type_comparator_type_ &
build_result_tWhat a bulk build produced, and why it stopped where it did.
basic_avl_tree.hpp:528 ↗
struct build_result_t

Not an expected, which holds a value or a reason and never both: a refused build still owns every node it made, and whoever asked for it has to free them.

Fields

rootnode_t *Everything that was built, which the owner frees whether the build finished or not.
statusstatus_tWhy the build stopped: a refused node, a refused element copy, or success_k.
countstd::size_tHow many nodes hang off root, short of the range length on a partial build.
split_result_tResult of a split operation.
basic_avl_tree.hpp:749 ↗
struct split_result_t

Fields

leftnode_t *Subtree with all elements ordered before the split key.
rightnode_t *Subtree with all elements not ordered before the split key.
Classbasic_avl_treeException-free AVL tree container with ordered storage similar to std::set, managing allocation and providing high-level tree operations with status-based error handling.54
basic_avl_tree.hpp:1129 ↗
template <typename value_type_,
          typename comparator_type_ = less_t,
          typename allocator_type_ = std::allocator<basic_avl_node<value_type_, comparator_type_>>>
class basic_avl_tree

API Design

This AVL tree provides an STL-compatible interface:

  • Bidirectional iterators for in-order traversal (begin/end/rbegin/rend)
  • Returns status_t for some operations instead of throwing exceptions
  • Supports heterogeneous lookups when comparator defines is_transparent
  • Provides both insert (fails if exists) and upsert (always succeeds) semantics
  • Allows custom allocators for all internal nodes
  • Iterator invalidation: only iterators to erased elements are invalidated

Callback-based APIs are also provided for convenience (find, range, equal_range). The tree maintains O(log n) height through automatic rebalancing after modifications.

Entry Constraints

Entries stored in the tree must be comparable using the provided comparator. They must also be noexcept move-constructible and assignable to ensure safety. When dealing with bulk-insertions of non-copyable types, make sure to use a moving iterator, like the std::move_iterator.

Insert Strategies

Four distinct modification strategies with different failure handling:

MethodKey ExistsReturnsUse Case
insert()Failsinvalid_arg_kStrict, ensure key is new
insert_if_missing()Skipssuccess_kLenient, insert only if absent
upsert()Overwritessuccess_kAlways succeeds, insert or overwrite
update()Failskey_not_found_kStrict, key must already exist
key_tExtracts a map's key_type through SFINAE, or falls back to value_type for a set.
basic_avl_tree.hpp:1144 ↗
using key_t = typename mapping_key_type_or_itself<value_t>::type;
mapped_tExtracts a map's mapped_type through SFINAE, or falls back to void for a set.
basic_avl_tree.hpp:1148 ↗
using mapped_t = typename mapped_value_type_or_void<value_t>::type;
rebindRebind this tree type to different entry and comparator types.
basic_avl_tree.hpp:1167 ↗
template <typename other_value_type_, typename other_comparator_type_>
using rebind = basic_avl_tree<other_value_type_, other_comparator_type_, typename std::allocator_traits<allocator_t>::template rebind_alloc<basic_avl_node<other_value_type_, other_comparator_type_>>>;

Follows STL allocator rebind pattern for type transformations.

basic_avl_treeSeeds both policies, which a stateful comparator needs to survive a rebind.
basic_avl_tree.hpp:1469 ↗
basic_avl_tree() noexcept=default;
basic_avl_tree(allocator_t allocator) noexcept;
basic_avl_tree(comparator_t comparator, allocator_t allocator) noexcept;

Seeds both policies, which a stateful comparator needs to survive a rebind.

basic_avl_tree(basic_avl_tree && other) noexcept;
basic_avl_tree(basic_avl_tree const &)=delete;
operator=
basic_avl_tree.hpp:1481 ↗
basic_avl_tree & operator=(basic_avl_tree const &)=delete;
basic_avl_tree & operator=(basic_avl_tree && other) noexcept;
~basic_avl_tree
basic_avl_tree.hpp:1493 ↗
~basic_avl_tree() noexcept;
copyCreates a deep copy of the tree.
basic_avl_tree.hpp:1504 ↗
expected<basic_avl_tree> copy() const noexcept;

Returns

Copy of the tree.

Notes

This operation may fail due to allocation errors during node duplication. The returned tree uses a copy of this tree's allocator.

Statuses

*× 2Returns the number of elements in the tree.
basic_avl_tree.hpp:1525 ↗
std::size_t size() const noexcept;

Returns

Number of elements.

heightReturns the height of the tree (number of edges in longest path from root to leaf).
basic_avl_tree.hpp:1531 ↗
std::size_t height() noexcept;

Returns

Height of the tree, 0 if empty.

rootReturns raw pointer to the root node.
basic_avl_tree.hpp:1537 ↗
node_t * root() const noexcept;

For internal use.

Returns

The root node, or nullptr when the tree is empty.

allocatorReturns the allocator associated with the tree.
basic_avl_tree.hpp:1543 ↗
allocator_t & allocator() noexcept;
allocator_t const & allocator() const noexcept;

Returns the allocator associated with the tree (const version).

Returns

The tree's allocator, for callers that need to allocate alongside it.

beginReturns an iterator to the first element (minimum) in the tree.
basic_avl_tree.hpp:1559 ↗
iterator begin() noexcept;
const_iterator begin() const noexcept;

Returns a const iterator to the first element (minimum) in the tree.

Returns

Iterator to the minimum element, or end() if empty.

cbeginReturns a const iterator to the first element (minimum) in the tree.
basic_avl_tree.hpp:1571 ↗
const_iterator cbegin() const noexcept;

Returns

Const iterator to the minimum element, or end() if empty.

endReturns an iterator to one past the last element.
basic_avl_tree.hpp:1577 ↗
iterator end() noexcept;
const_iterator end() const noexcept;

Returns a const iterator to one past the last element.

Returns

End iterator (points to nullptr).

cendReturns a const iterator to one past the last element.
basic_avl_tree.hpp:1589 ↗
const_iterator cend() const noexcept;

Returns

End iterator (points to nullptr).

rbeginReturns a reverse iterator to the reversed tree's first element, the maximum.
basic_avl_tree.hpp:1595 ↗
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;

Returns a const reverse iterator to the reversed tree's first element, the maximum.

Returns

Reverse iterator to the maximum element, or rend() if empty.

crbeginReturns a const reverse iterator to the reversed tree's first element, the maximum.
basic_avl_tree.hpp:1607 ↗
const_reverse_iterator crbegin() const noexcept;

Returns

Const reverse iterator to the maximum element, or rend() if empty.

rendReverse iterator one past the reversed tree's last element, just before the minimum.
basic_avl_tree.hpp:1613 ↗
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;

Returns a const reverse iterator to one past the last element of the reversed tree, before the minimum.

Returns

Reverse end iterator.

crendReturns a const reverse iterator to one past the last element of the reversed tree, before the minimum.
basic_avl_tree.hpp:1627 ↗
const_reverse_iterator crend() const noexcept;

Returns

Const reverse end iterator.

findFinds an element equal to the given comparable.
basic_avl_tree.hpp:1648 ↗
template <typename comparable_type_>
iterator find(comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
const_iterator find(comparable_type_ && comparable) const noexcept;

Finds an element equal to the given comparable (const version).

Heterogeneous lookup supported if comparator defines is_transparent.

Parameters

comparableinObject comparable to value_t.

Returns

Iterator to found element, or end() if not found.

find_copyFinds and returns a copy of an element equal to comparable.
basic_avl_tree.hpp:1677 ↗
template <typename comparable_type_>
expected<value_t> find_copy(
    comparable_type_ && comparable) const noexcept;

Convenience method to avoid callback-based access in tests and simple use cases. Heterogeneous lookup supported if comparator defines is_transparent.

Parameters

comparableinObject comparable to value_t.

Returns

Result with copied element.

Statuses

  • key_not_found_k No element is equal to comparable.
  • Any status the element's copy returns passes through unchanged.
lower_bound_copyFinds and returns a copy of the first element not less than (>=) comparable.
basic_avl_tree.hpp:1694 ↗
template <typename comparable_type_>
expected<value_t> lower_bound_copy(
    comparable_type_ && comparable) const noexcept;

Parameters

comparableinObject comparable to value_t.

Returns

Result with copied element.

Statuses

  • key_not_found_k Every element is less than comparable.
  • Any status the element's copy returns passes through unchanged.
upper_bound_copyFinds and returns a copy of the first element greater than (>) comparable.
basic_avl_tree.hpp:1711 ↗
template <typename comparable_type_>
expected<value_t> upper_bound_copy(
    comparable_type_ && comparable) const noexcept;

Parameters

comparableinObject comparable to value_t.

Returns

Result with copied element.

Statuses

  • key_not_found_k No element is greater than comparable.
  • Any status the element's copy returns passes through unchanged.
containsChecks if an element equal to comparable exists in the tree.
basic_avl_tree.hpp:1725 ↗
template <typename comparable_type_>
expected<bool> contains(comparable_type_ && comparable) const noexcept;

Heterogeneous lookup supported if comparator defines is_transparent.

Parameters

comparableinObject comparable to value_t.

Returns

Always success; a lookup over owned nodes has nothing to refuse.

lower_boundFinds the first element not less than (>=) the given comparable.
basic_avl_tree.hpp:1737 ↗
template <typename comparable_type_>
iterator lower_bound(comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
const_iterator lower_bound(
    comparable_type_ && comparable) const noexcept;

Finds the first element not less than (>=) the given comparable (const version).

Heterogeneous lookup supported if comparator defines is_transparent.

Parameters

comparableinObject comparable to value_t.

Returns

Iterator to found element, or end() if all elements are less.

upper_boundFinds the first element greater than (>) the given comparable.
basic_avl_tree.hpp:1762 ↗
template <typename comparable_type_>
iterator upper_bound(comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
const_iterator upper_bound(
    comparable_type_ && comparable) const noexcept;

Finds the first element greater than (>) the given comparable (const version).

Heterogeneous lookup supported if comparator defines is_transparent.

Parameters

comparableinObject comparable to value_t.

Returns

Iterator to found element, or end() if no element is greater.

countReturns the number of elements with key equal to the specified argument.
basic_avl_tree.hpp:1787 ↗
template <typename comparable_type_>
expected<std::size_t> count(
    comparable_type_ && comparable) const noexcept;

For unique-key containers like this, returns either 0 or 1.

Parameters

comparableinObject comparable to value_t and convertible to search key.

Returns

Always success; a lookup over owned nodes has nothing to refuse.

emptyChecks if the tree has no elements.
basic_avl_tree.hpp:1795 ↗
bool empty() const noexcept;

Returns

True if empty, false otherwise.

equal_rangeReturns a range of elements matching a specific key.
basic_avl_tree.hpp:1806 ↗
template <typename comparable_type_>
std::pair<iterator, iterator> equal_range(
    comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
std::pair<const_iterator, const_iterator> equal_range(
    comparable_type_ && comparable) const noexcept;

Returns a range of elements matching a specific key (const version).

template <typename comparable_type_ = value_t, typename callback_type_ = no_op_t>
status_t equal_range(
    comparable_type_ && comparable,
    callback_type_ && callback) const noexcept;

Finds all elements equal to a single key.

For unique-key containers, returns range containing at most one element.

Parameters

comparableinObject comparable to value_t.
callbackinCallback invoked for each element equal to the key. Must be noexcept.

Returns

Pair of iterators [first, last) where all elements are equal to key. If key not found, both iterators equal end().

rangeIterates over all entries in the range [ lower, upper).
basic_avl_tree.hpp:1855 ↗
template <typename lower_type_ = value_t, typename upper_type_ = value_t, typename callback_type_ = no_op_t>
status_t range(
    lower_type_ && lower,
    upper_type_ && upper,
    callback_type_ && callback) const noexcept;
template <typename lower_type_ = value_t, typename upper_type_ = value_t, typename callback_type_ = no_op_t>
status_t range(
    lower_type_ && lower,
    upper_type_ && upper,
    callback_type_ && callback) noexcept;

Iterates over all entries in the range [ lower, upper), allowing in-place modification.

Const version. Invokes callback for each element in the specified range.

Parameters

lowerinLower bound of the range (inclusive).
upperinUpper bound of the range (exclusive).
callbackinCallback invoked for each element in range. Must be noexcept.

Notes

A callback answering walk_control_t stops the walk where it says to.

erase_rangeErases a range of elements [lower, upper).
basic_avl_tree.hpp:1908 ↗
template <typename lower_type_, typename upper_type_, typename callback_type_ = no_op_t>
void erase_range(
    lower_type_ && lower,
    upper_type_ && upper,
    callback_type_ && callback = {}) noexcept;

Invokes callback for each erased element. Complexity: O(log N + K), where K is the number of elements in the range.

Parameters

lowerinLower bound of the range (inclusive).
upperinUpper bound of the range (exclusive).
callbackinOptional callback invoked for each erased element.
insert_if_missingInserts an entry only if the key doesn't exist.
basic_avl_tree.hpp:1970 ↗
template <typename comparable_type_>
inserted_iterator_t insert_if_missing(
    comparable_type_ && comparable) noexcept;
template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t insert_if_missing(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;

Inserts every element of [ first, last ) whose key is free, keeping incumbents.

status_t insert_if_missing(
    std::initializer_list<value_t> ilist) noexcept;

Inserts elements from an initializer list if keys don't exist.

Matches std::set::insert() semantics. Leaves any incumbent alone, and leaves comparable alone unless a node was made for it.

Parameters

comparableinEntry to insert (moved into the tree).
firstinBeginning of range to insert.
lastinEnd of range to insert.
tagsinMarkers promising something about the range, such as assume_sorted_t.
ilistinInitializer list of entries to insert.

Returns

The position the key lives at, and whether it was made or matched.

Statuses

  • refused_k The key is absent and the allocator refused its node, so nothing was stored.
  • Any status an element's copy returns passes through unchanged.

Notes

Complexity: O(n log n) to build, or O(n) under assume_sorted_t, then O(merge).

insertAtomically inserts an entry only if the key doesn't exist.
basic_avl_tree.hpp:2010 ↗
template <typename comparable_type_>
expected<iterator> insert(comparable_type_ && comparable) noexcept;
iterator insert(const_iterator, value_t const &) noexcept=delete;

Deleted: Hint-based insert is not supported.

iterator insert(const_iterator, value_t &&) noexcept=delete;

Deleted: Hint-based insert is not supported.

template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t insert(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;

Inserts every element of [ first, last ), refusing the batch over a taken key.

Fails with error if key exists. This is strict insert semantics - ensures key is new.

Callers that want the incumbent's position ask insert_if_missing() instead, which hands back both.

Parameters

comparableinEntry to insert, moved into the tree only when a node is made for it.
firstinBeginning of range to insert.
lastinEnd of range to insert.
tagsinMarkers promising something about the range, such as assume_sorted_t.

Returns

Iterator to the new element.

Statuses

  • key_already_exists_k An incumbent holds the key, or an inconsistent comparator matched one while linking the node.
  • key_already_exists_k A key repeats inside an unsorted range or is already in this tree.
  • Any status an element's copy returns passes through unchanged.

Notes

Complexity: O(n log n) to build or O(n) under assume_sorted_t, plus O(m+n) checks.

upsertAtomically inserts or updates an entry.
basic_avl_tree.hpp:2054 ↗
template <typename comparable_type_>
upserted_node_t upsert(comparable_type_ && comparable) noexcept;
template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t upsert(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;

Upserts a range of entries (inserts new keys or updates existing ones).

status_t upsert(std::initializer_list<value_t> ilist) noexcept;

Upserts elements from initializer list (insert or update semantics).

Overwrites existing entry if key exists (upsert semantics).

Parameters

comparableinEntry to upsert (moved into the tree).
firstinBeginning of range to upsert.
lastinEnd of range to upsert.
tagsinMarkers promising something about the range, such as assume_sorted_t.
ilistinInitializer list of entries to upsert.

Returns

The node the entry now lives in, and whether it was made or matched.

Statuses

  • refused_k The key is absent and the allocator refused its node, so nothing was stored.
  • Any status an element's copy returns passes through unchanged.

Notes

  • With assume_sorted_t : Range must be sorted (ascending order).
  • With assume_unique_t : Range must have no duplicate keys with existing tree; violating it is undefined behavior.
  • Atomicity: every failure happens while the staging tree is built, leaving this tree unchanged; the merge itself only relinks nodes, so it cannot fail part-way. Duplicate keys are UPDATED during the merge, not skipped.
emplaceConstructs an element in-place.
basic_avl_tree.hpp:2092 ↗
template <typename... args_types_>
std::pair<iterator, bool> emplace(args_types_ &&... args) noexcept;

Matches std::set::emplace() semantics. Does not insert if key already exists.

Parameters

argsinArguments to forward to value_t constructor.

Returns

Pair of iterator to inserted/existing element and bool indicating success.

Notes

The STL shape collapses a refused allocation and a matched incumbent into the same false, so insert_if_missing() is the call that tells them apart.

emplace_hintDeleted: Hint-based emplace is not supported.
basic_avl_tree.hpp:2100 ↗
template <typename... args_types_>
iterator emplace_hint(
    const_iterator,
    args_types_ && ...) noexcept=delete;

AVL trees don't benefit from position hints, and providing unused hints is misleading. Use emplace() instead.

updateUpdates a range of existing entries, all-or-nothing on failure.
basic_avl_tree.hpp:2339 ↗
template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t update(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;
status_t update(std::initializer_list<value_t> ilist) noexcept;

Updates elements from an initializer list (all keys must exist).

Builds a temporary tree from the range, validates all keys exist, then updates in one step. On allocation failure or any missing key, this tree remains unchanged.

Parameters

firstinBeginning of range to update.
lastinEnd of range to update.
tagsinMarkers promising something about the range, such as assume_sorted_t.
ilistinInitializer list of entries to update.

Notes

Complexity:

  • With assume_sorted_t : O(n) build + O(m+n) validation + O(m log n) update
  • Without: O(n log n) build + O(m+n) validation + O(m log n) update

With assume_sorted_t : Range must be sorted (ascending order).

All-or-nothing on failure: if any key is missing, nothing is updated. Temporary tree is destroyed via RAII, this tree remains unchanged.

Statuses

  • key_not_found_k A key of the range is absent from this tree.
  • Any status an element's copy returns passes through unchanged.
  • key_not_found_k A key of the list is absent from this tree.
  • Any status an element's copy returns passes through unchanged.
key_compReturns the function object that compares keys.
basic_avl_tree.hpp:2377 ↗
comparator_t key_comp() const noexcept;

Returns

The comparison function object.

value_compReturns the function object that compares values, for sets the same as key_comp().
basic_avl_tree.hpp:2383 ↗
comparator_t value_comp() const noexcept;

Returns

The comparison function object.

max_sizeReturns the maximum possible number of elements.
basic_avl_tree.hpp:2389 ↗
std::size_t max_size() const noexcept;

Returns

Theoretical maximum size.

swapExchanges the contents of this tree with another.
basic_avl_tree.hpp:2405 ↗
status_t swap(basic_avl_tree & other) noexcept;

Swaps tree structure, size, comparator, and (if propagating) allocators. For non-propagating allocators (when propagate_on_container_swap is false), the allocators must be equal, otherwise invalid_argument_k is returned.

Parameters

otherinoutTree to swap with.

Statuses

eraseErases a single entry matching the given comparable.
basic_avl_tree.hpp:2470 ↗
template <typename comparable_type_ = value_t, typename callback_found_type_ = no_op_t, typename callback_missing_type_ = no_op_t>
void erase(
    comparable_type_ && comparable,
    callback_found_type_ && callback_found,
    callback_missing_type_ && callback_missing) noexcept;
template <typename comparable_type_>
bool erase(comparable_type_ && comparable) noexcept;
erase_result_t erase(iterator position) noexcept;

Erases the element at the specified iterator position.

erase_result_t erase(const_iterator position) noexcept;

Erases the element at the specified const_iterator position.

erase_result_t erase(iterator first, iterator last) noexcept;

Erases all elements in the range [first, last).

erase_result_t erase(
    const_iterator first,
    const_iterator last) noexcept;

Erases all elements in the range [first, last) using const_iterators.

No callbacks.

Parameters

comparableinObject comparable to value_t and convertible to search key.
callback_foundinCallback to receive the erased entry. Must be noexcept.
callback_missinginCallback triggered if nothing was found. Must be noexcept.
positioninIterator to element to erase. Must be valid and dereferenceable.
firstinBeginning of range to erase.
lastinEnd of range to erase (not erased).

Notes

If position is end(), returns {end(), success} without modifying the tree.

reserveHints to the tree to pre-allocate memory.
basic_avl_tree.hpp:2565 ↗
void reserve(std::size_t) noexcept;

No-op for AVL tree implementation. Provided for API consistency with other containers. Doesn't guarantee subsequent insertions won't fail with "out of memory".

Suggested capacity (ignored for AVL trees).

Notes

This is a no-op because AVL trees don't support reserving capacity efficiently.

clearRemoves all elements from the tree and frees their memory.
basic_avl_tree.hpp:2568 ↗
void clear() noexcept;
for_eachVisits every element in sorted order; a callback can stop it early via walk_control_t.
basic_avl_tree.hpp:2579 ↗
template <typename callback_type_>
status_t for_each(callback_type_ && callback) noexcept;
mergeMerges another tree into this one, transferring all nodes.
basic_avl_tree.hpp:2599 ↗
void merge(basic_avl_tree & other) noexcept;
void merge(basic_avl_tree & other, assume_unique_t) noexcept;

Merges another tree into this one with optimized algorithm for disjoint trees.

void merge(extract_result_t other) noexcept;

Merges a single extracted node into this tree, deallocating it if the key exists.

Elements with keys that already exist in this tree are deallocated (not kept in source).

Parameters

otherinoutTree to merge from. Will be empty after merge.

Notes

  • Unlike std::set::merge(), nodes with duplicate keys are deallocated rather than remaining in the source container. This ensures no memory leaks in a noexcept context.
  • Complexity: O(m log n) where m = other.size(), n = this.size(). For disjoint trees, use merge(other, assume_unique) for faster O(m log(n/m+1)) or O(m+n).
  • Complexity: O(m log(n/m + 1)) for unbalanced sizes, O(m+n) for similar sizes.
  • Unlike std::set::merge(), a node with a duplicate key is deallocated rather than being returned. This ensures no memory leaks in a noexcept context.

Warnings

If precondition violated (duplicate keys exist), behavior is undefined.

splitSplits the tree at a given key into two trees.
basic_avl_tree.hpp:2736 ↗
template <typename comparable_type_>
split_result_t split(comparable_type_ && comparable) noexcept;

Elements < key go to left tree, elements >= key go to right tree. This tree becomes empty after the split.

Parameters

comparableinKey to split at.

Returns

Contains left tree (< key) and right tree (>= key).

Notes

This operation is O(log n) and maintains AVL balance in both resulting trees. The current tree is emptied (moved-from state).

joinJoins this tree with another tree.
basic_avl_tree.hpp:2770 ↗
void join(basic_avl_tree & other) noexcept;

Precondition: All elements in this tree < all elements in other tree. The other tree becomes empty after the join.

Parameters

otherinoutTree to join with (must have all larger elements).

Notes

This operation is O(log n) and maintains AVL balance. If the precondition is violated, the resulting tree structure is undefined.

erase_result_tResult of an erase operation on an iterator: the next-element iterator plus the outcome status.
basic_avl_tree.hpp:1176 ↗
struct erase_result_t

Fields

nextiteratorIterator to the element following the erased one, or end().
statusstatus_tStatus of the erase operation.
iteratorBidirectional iterator for AVL tree.
basic_avl_tree.hpp:1186 ↗
class iterator

Provides in-order traversal of tree elements.

const_iteratorConst bidirectional iterator for AVL tree, providing read-only in-order traversal of tree elements.
basic_avl_tree.hpp:1238 ↗
class const_iterator
upserted_node_tThe node an upsert settled on, and how it got there.
basic_avl_tree.hpp:1930 ↗
struct upserted_node_t

Assigning to the result overwrites that node's entry, which is what makes it usable as a handle rather than a report.

Fields

nodenode_t *
placementnode_t::node_placement_t
inserted_iterator_tWhere an insertion settled, and how it got there.
basic_avl_tree.hpp:1945 ↗
struct inserted_iterator_t

Names the same three outcomes as upserted_node_t, one level up from the nodes.

Fields

positioniteratorThe element's position, which is end() when nothing was stored.
placementnode_t::node_placement_tWhether the entry was made, matched an incumbent, or refused for want of memory.
split_result_tResult of a split operation on a tree.
basic_avl_tree.hpp:2716 ↗
struct split_result_t

Fields

leftbasic_avl_treeTree with all elements ordered before the split key.
rightbasic_avl_treeTree with all elements not ordered before the split key.
Classbasic_fixed_poolAt most capacity records of value_type_, taken and vacated by index.13
basic_fixed_pool.hpp:52 ↗
template <typename value_type_,
          std::unsigned_integral index_type_ = std::uint32_t,
          allocator_of<std::byte> allocator_type_ = default_allocator_t>
class basic_fixed_pool
absent_kWhat take answers when every record is taken.
basic_fixed_pool.hpp:59 ↗
index_t absent_k;
basic_fixed_pool
basic_fixed_pool.hpp:106 ↗
basic_fixed_pool() noexcept=default;
basic_fixed_pool(allocator_t allocator) noexcept;
basic_fixed_pool(basic_fixed_pool && other) noexcept;
basic_fixed_pool(basic_fixed_pool const &)=delete;
~basic_*_pool× 2
basic_fixed_pool.hpp:108 ↗
~basic_fixed_pool() noexcept;
Symbol
~basic_fixed_pool
~basic_growing_pool
operator=
basic_fixed_pool.hpp:114 ↗
basic_fixed_pool & operator=(basic_fixed_pool && other) noexcept;
basic_fixed_pool & operator=(basic_fixed_pool const &)=delete;
recordsEvery record, taken or free, in index order.
basic_fixed_pool.hpp:158 ↗
std::span<value_t> records() noexcept;
std::span<value_t const> records() const noexcept;
operator[]The record at index, taken or not, without bounds checking.
basic_fixed_pool.hpp:162 ↗
value_t & operator[](index_t index) noexcept;
value_t const & operator[](index_t index) const noexcept;
index_ofThe index of record, which must be one of records.
basic_fixed_pool.hpp:172 ↗
index_t index_of(value_t const & record) const noexcept;
takeTakes a free record, as its last holder left it, or answers absent_k when none is.
basic_fixed_pool.hpp:178 ↗
index_t take() noexcept;
*× 2Frees the taken record at index, stepping its generation so older handles go stale.
basic_fixed_pool.hpp:181 ↗
void vacate(index_t index) noexcept;
handle_*_of× 2A handle to the record at index, which at answers until it is vacated.
basic_fixed_pool.hpp:188 ↗
requires generation_tagged<value_t, index_t>
handle_t handle__of(index_t index) const noexcept;
atThe record handle names, or null once that record was vacated.
basic_fixed_pool.hpp:196 ↗
requires generation_tagged<value_t, index_t>
value_t * at(handle_t handle) noexcept;
requires generation_tagged<value_t, index_t>
value_t const * at(handle_t handle) const noexcept;
makeA pool of capacity default-constructed records, all of them free.
basic_fixed_pool.hpp:134 ↗
expected<basic_fixed_pool> make(
    std::size_t capacity,
    allocator_t allocator = {}) noexcept;

Returns

The pool, which allocates nothing for a zero capacity.

Statuses

handle_tA record's index and the generation it was taken at, for a tagged record only.
basic_fixed_pool.hpp:62 ↗
struct handle_t

Fields

indexindex_t
generationindex_t
Classbasic_flat_setA sorted set of unique value_type_ ordered by comparator_type_.22
basic_flat_set.hpp:33 ↗
template <typename value_type_,
          typename comparator_type_ = less_t,
          row_kit row_kit_type_ = native_row_kit_t,
          typename allocator_type_ = default_allocator<value_type_>>
class basic_flat_set

The rank search goes through row_kit_type_ when the element is a row_searchable_key ordered by less_t, and through a binary search with the comparator otherwise.

key_tThe key half, which for a set is the whole element.
basic_flat_set.hpp:41 ↗
using key_t = value_t;
mapped_tThe mapped half, which a set never has.
basic_flat_set.hpp:45 ↗
using mapped_t = void;
basic_flat_set
basic_flat_set.hpp:80 ↗
basic_flat_set() noexcept=default;
basic_flat_set(
    comparator_t comparator,
    allocator_t allocator = {}) noexcept;
basic_flat_set(basic_flat_set &&) noexcept=default;
basic_flat_set(basic_flat_set const &)=delete;
operator=
basic_flat_set.hpp:86 ↗
basic_flat_set & operator=(basic_flat_set &&) noexcept=default;
basic_flat_set & operator=(basic_flat_set const &)=delete;
copyA deep copy.
basic_flat_set.hpp:104 ↗
expected<basic_flat_set> copy() const noexcept;

Statuses

  • Any status an element's copy returns passes through unchanged.
operator[]The element at position ordinal in sorted order.
basic_flat_set.hpp:130 ↗
value_t const & operator[](std::size_t ordinal) const noexcept;
rankHow many elements order below wanted, which is also where it is or would be inserted.
basic_flat_set.hpp:138 ↗
template <typename comparable_type_>
std::size_t rank(comparable_type_ const & wanted) const noexcept;
findThe element equivalent to wanted, or null.
basic_flat_set.hpp:159 ↗
template <typename comparable_type_>
value_t const * find(comparable_type_ const & wanted) const noexcept;
containsWhether an element equivalent to wanted is here.
basic_flat_set.hpp:168 ↗
template <typename comparable_type_>
expected<bool> contains(comparable_type_ const & wanted) const noexcept;

Always success; a sorted array cannot refuse a read.

lower_boundThe first element not below wanted, or end.
basic_flat_set.hpp:174 ↗
template <typename comparable_type_>
value_t const * lower_bound(
    comparable_type_ const & wanted) const noexcept;
upper_boundThe first element above wanted, or end.
basic_flat_set.hpp:180 ↗
template <typename comparable_type_>
value_t const * upper_bound(
    comparable_type_ const & wanted) const noexcept;
find_copyCopies out the element equivalent to wanted.
basic_flat_set.hpp:194 ↗
template <typename comparable_type_>
expected<value_t> find_copy(
    comparable_type_ const & wanted) const noexcept;

Statuses

  • key_not_found_k No element is equivalent to wanted.
  • Any status the element's copy returns passes through unchanged.
lower_bound_copyCopies out the first element not below wanted.
basic_flat_set.hpp:208 ↗
template <typename comparable_type_>
expected<value_t> lower_bound_copy(
    comparable_type_ const & wanted) const noexcept;

Statuses

  • key_not_found_k Every element is below wanted.
  • Any status the element's copy returns passes through unchanged.
upper_bound_copyCopies out the first element above wanted.
basic_flat_set.hpp:222 ↗
template <typename comparable_type_>
expected<value_t> upper_bound_copy(
    comparable_type_ const & wanted) const noexcept;

Statuses

  • key_not_found_k No element is above wanted.
  • Any status the element's copy returns passes through unchanged.
rangeVisits every element in [lower, upper) in sorted order, or fewer if the callback halts.
basic_flat_set.hpp:230 ↗
template <typename lower_type_ = value_t, typename upper_type_ = value_t, typename callback_type_ = no_op_t>
status_t range(
    lower_type_ const & lower,
    upper_type_ const & upper,
    callback_type_ && callback) const noexcept;
insertAdds element, or leaves the set alone.
basic_flat_set.hpp:248 ↗
status_t insert(value_t element) noexcept;
template <readable_iterator input_iterator_type_>
status_t insert(
    input_iterator_type_ first,
    input_iterator_type_ last) noexcept;

Adds every element of [ first, last ), refusing the batch over any taken key.

All-or-nothing over this set from the first element on, a taken key included: the whole range is staged and checked before the merge absorbing it allocates anything.

Statuses

  • key_already_exists_k A key repeats inside the range or is already in this set.
  • Any status an element's copy returns passes through unchanged.
upsertAdds element, replacing an equivalent one if the set holds it.
basic_flat_set.hpp:255 ↗
status_t upsert(value_t element) noexcept;
template <readable_iterator input_iterator_type_>
status_t upsert(
    input_iterator_type_ first,
    input_iterator_type_ last) noexcept;

Writes every element of [ first, last ), replacing any equivalent element.

All-or-nothing over this set from the first element on; an element already here is overwritten by the merge, which moves rather than copies once its buffer is secured.

Statuses

  • Any status an element's copy returns passes through unchanged.
insert_if_missingAdds every element of [ first, last ) this set lacks, leaving incumbents alone.
basic_flat_set.hpp:274 ↗
template <readable_iterator input_iterator_type_>
status_t insert_if_missing(
    input_iterator_type_ first,
    input_iterator_type_ last) noexcept;

All-or-nothing over this set from the first element on: the staging set holds every element before the merge begins, and the merge makes its one allocation before it moves anything.

Statuses

  • Any status an element's copy returns passes through unchanged.
eraseRemoves the element equivalent to wanted.
basic_flat_set.hpp:331 ↗
template <typename comparable_type_>
status_t erase(comparable_type_ const & wanted) noexcept;

Statuses

erase_rangeErases the half-open range [lower, upper), handing each removed element to callback first.
basic_flat_set.hpp:341 ↗
template <typename lower_type_, typename upper_type_, typename callback_type_ = no_op_t>
void erase_range(
    lower_type_ const & lower,
    upper_type_ const & upper,
    callback_type_ && callback = {}) noexcept;
erase_ifRemoves every element predicate admits, handing each to callback first, and answers how many went.
basic_flat_set.hpp:352 ↗
template <typename predicate_type_, typename callback_type_ = no_op_t>
requires std::predicate<predicate_type_ &, value_t &>
std::size_t erase_if(
    predicate_type_ && predicate,
    callback_type_ && callback = {}) noexcept;
makeAn empty set with room for capacity elements.
basic_flat_set.hpp:91 ↗
expected<basic_flat_set> make(
    std::size_t capacity,
    comparator_t comparator = {},
    allocator_t allocator = {}) noexcept;
Classbasic_growing_poolRecords of value_type_, taken and vacated by index, growing chunk by chunk up to a limit without moving a record, and readable by index or handle from other threads while the one owner grows it.16
basic_growing_pool.hpp:66 ↗
template <typename value_type_,
          std::unsigned_integral index_type_ = std::uint32_t,
          allocator_of<std::byte> allocator_type_ = default_allocator_t>
class basic_growing_pool
absent_kWhat take answers when every record is taken and no more may be added.
basic_growing_pool.hpp:73 ↗
index_t absent_k;
capacity_max_kThe most records a pool holds, every index but absent_k.
basic_growing_pool.hpp:76 ↗
std::size_t capacity_max_k;
basic_growing_pool
basic_growing_pool.hpp:240 ↗
basic_growing_pool() noexcept=default;
basic_growing_pool(allocator_t allocator) noexcept;
basic_growing_pool(basic_growing_pool && other) noexcept;
basic_growing_pool(basic_growing_pool const &)=delete;
operator=
basic_growing_pool.hpp:246 ↗
basic_growing_pool & operator=(basic_growing_pool && other) noexcept;
basic_growing_pool & operator=(basic_growing_pool const &)=delete;
capacityRecords the pool holds now, taken or free, bookkeeping of whoever serializes take.
basic_growing_pool.hpp:285 ↗
std::size_t capacity() const noexcept;
capacity_limitRecords the pool may grow to.
basic_growing_pool.hpp:288 ↗
std::size_t capacity_limit() const noexcept;
fullWhether every record is taken and none may be added.
basic_growing_pool.hpp:294 ↗
bool full() const noexcept;
recordsEvery record, taken or free, in index order.
basic_growing_pool.hpp:297 ↗
records_range<basic_growing_pool, value_t> records() noexcept;
records_range<basic_growing_pool const, value_t const> records() const noexcept;
operator[]The record at index, taken or not, whose chunk must be published.
basic_growing_pool.hpp:305 ↗
value_t & operator[](index_t index) noexcept;
value_t const & operator[](index_t index) const noexcept;
index_ofThe index of record, which must be one of records, found among published chunks.
basic_growing_pool.hpp:317 ↗
index_t index_of(value_t const & record) const noexcept;
takeTakes a free record, as its last holder left it, growing the pool when none is free and it may, or answers absent_k when it may not or the allocator refuses.
basic_growing_pool.hpp:330 ↗
index_t take() noexcept;
atThe record handle names, or null once that record was vacated.
basic_growing_pool.hpp:350 ↗
requires generation_tagged<value_t, index_t>
value_t * at(handle_t handle) noexcept;
requires generation_tagged<value_t, index_t>
value_t const * at(handle_t handle) const noexcept;
makeA pool of at least capacity free records, rounded up to a power of two and clipped to capacity_limit, which a take finding none free grows chunk by chunk up to that limit.
basic_growing_pool.hpp:268 ↗
expected<basic_growing_pool> make(
    std::size_t capacity,
    std::size_t capacity_limit,
    allocator_t allocator = {}) noexcept;

Returns

The pool, which allocates nothing for a zero limit.

Statuses

handle_tA record's index and the generation it was taken at, for a tagged record only.
basic_growing_pool.hpp:79 ↗
struct handle_t

Fields

indexindex_t
generationindex_t
records_walkA forward walk over every record, taken or free, in index order.
basic_growing_pool.hpp:92 ↗
template <typename pool_type_,
          typename record_type_>
class records_walk
records_rangeEvery record of a pool, taken or free, in index order, which a range-for walks.
basic_growing_pool.hpp:120 ↗
template <typename pool_type_,
          typename record_type_>
class records_range
Classbasic_hash_indexMaps fixed-width keys to record positions, at most capacity of them, with linear probing in windows and backward-shift erasure.8
basic_hash_index.hpp:49 ↗
template <window_key key_type_,
          std::unsigned_integral index_type_ = std::uint32_t,
          typename hasher_type_ = default_hash_t,
          allocator_of<std::byte> allocator_type_ = default_allocator_t,
          row_kit row_kit_type_ = native_row_kit_t>
class basic_hash_index
absent_kThe position find answers when no key matches, which no record may occupy.
basic_hash_index.hpp:57 ↗
index_t absent_k;
basic_hash_index
basic_hash_index.hpp:99 ↗
basic_hash_index() noexcept=default;
basic_hash_index(basic_hash_index && other) noexcept;
basic_hash_index(basic_hash_index const &)=delete;
operator=
basic_hash_index.hpp:101 ↗
basic_hash_index & operator=(basic_hash_index && other) noexcept;
basic_hash_index & operator=(basic_hash_index const &)=delete;
findThe position stored under key, the first in probe order when several are, or absent_k.
basic_hash_index.hpp:133 ↗
index_t find(key_t key) const noexcept;
template <typename confirm_type_>
index_t find(key_t key, confirm_type_ && confirm) const noexcept;

The first position under key that confirm accepts, for keys that hash something longer.

insertStores index under key, beside any position already stored under it.
basic_hash_index.hpp:162 ↗
status_t insert(key_t key, index_t index, assume_unique_t) noexcept;

Statuses

eraseRemoves the entry pairing key with index, shifting the rest of its run back.
basic_hash_index.hpp:182 ↗
status_t erase(key_t key, index_t index) noexcept;

Each later key in the run moves into the hole when the hole lies between its home and its slot, so every key stays reachable from its home without a tombstone.

Statuses

clearForgets every entry and keeps the allocation, zeroing the headers alone.
basic_hash_index.hpp:207 ↗
void clear() noexcept;
makeAn empty index accepting up to capacity keys, over twice as many slots.
basic_hash_index.hpp:116 ↗
expected<basic_hash_index> make(
    std::size_t capacity,
    hasher_t hasher = {},
    allocator_t allocator = {}) noexcept;

Returns

The index; a zero capacity allocates nothing and accepts nothing.

Statuses

Classbasic_hash_tableGrowable hash table with linear probing and Structure-of-Arrays layout.49
basic_hash_table.hpp:168 ↗
template <typename value_type_,
          typename hasher_type_ = default_hash_t,
          typename equals_type_ = equal_to_t,
          allocator_of<std::byte> allocator_type_ = default_allocator_t,
          row_kit row_kit_type_ = native_row_kit_t>
class basic_hash_table

Compatible with std::unordered_set interface. See file header for detailed design rationale.

See also

upsert_result_tWhat a probe that may store an element managed to do.
basic_hash_table.hpp:223 ↗
enum upsert_result_t

Values

inserted_kA free or tombstoned slot took the new element.
updated_kAn equal key was already there, and the callback saw it.
no_slot_kEvery slot along the probe sequence was taken by a different key.
no_memory_kA new key needed a larger table, and the allocator refused to supply one.
reserve_result_tWhat a growth attempt did to the table.
basic_hash_table.hpp:257 ↗
enum reserve_result_t

Values

unchanged_kThe capacity already covered the request, so every iterator stays valid.
reallocated_kA larger buffer replaced the old one, invalidating every iterator.
failed_kThe allocator refused, leaving the table exactly as it was.
emplace_report_tWhat an insertion hands back: nothing at all, or where the element landed and how.
basic_hash_table.hpp:855 ↗
enum emplace_report_t

Values

no_result_k
insert_result_k
value_tThe whole stored element: the key for a set, a mapping view of both halves for a map.
basic_hash_table.hpp:174 ↗
using value_t = typename layout_t::value_t;
key_tThe key half, which is the whole element for a set.
basic_hash_table.hpp:177 ↗
using key_t = typename layout_t::key_t;
mapped_tThe mapped half, void for a set.
basic_hash_table.hpp:180 ↗
using mapped_t = typename layout_t::mapped_t;
owned_value_typeThe element as an owned pair, which value_type cannot be: it views two regions.
basic_hash_table.hpp:280 ↗
using owned_value_type = typename layout_t::value_copy_t;
is_associativeTraits the transactional adapters dispatch on.
basic_hash_table.hpp:294 ↗
using is_associative = std::bool_constant<has_values_k>;
rebindRestates this table over a different element type, hasher and equality.
basic_hash_table.hpp:301 ↗
template <typename other_element_type_, typename other_hasher_type_, typename other_equals_type_ = equals_t>
using rebind = basic_hash_table<other_element_type_, other_hasher_type_, other_equals_type_, allocator_t, row_kit_type_>;

A store that decorates its elements - wrapping them in version metadata, say - needs the equality restated alongside the hasher, since both must address the decorated shape.

basic_hash_tableDefault constructor, avoiding memory allocations, accepting a pre-constructed hasher functor, an equality operator, and the allocator state.
basic_hash_table.hpp:404 ↗
basic_hash_table(
    hasher_t hasher = {},
    equals_t equals = {},
    allocator_t allocator = {}) noexcept;
basic_hash_table(basic_hash_table && other) noexcept;
basic_hash_table(basic_hash_table const &) noexcept=delete;
basic_hash_table(
    offset_t,
    hasher_t = {},
    equals_t = {},
    allocator_t = {}) noexcept=delete;

See also

operator=
basic_hash_table.hpp:409 ↗
basic_hash_table & operator=(basic_hash_table && other) noexcept;
basic_hash_table & operator=(basic_hash_table const &) noexcept=delete;
deleted_countTombstones left behind by erasures, which only a rehash reclaims.
basic_hash_table.hpp:472 ↗
offset_t deleted_count() const noexcept;
bucket_countSTL-compatibility function.
basic_hash_table.hpp:489 ↗
offset_t bucket_count() const noexcept;

See also

max_bucket_countSTL-compatibility function.
basic_hash_table.hpp:495 ↗
offset_t max_bucket_count() const noexcept;

See also

hash_functionSTL-compatibility function.
basic_hash_table.hpp:501 ↗
hasher_t hash_function() const noexcept;

See also

key_eqSTL-compatibility function.
basic_hash_table.hpp:507 ↗
equals_t key_eq() const noexcept;

See also

get_allocatorSTL-compatibility function.
basic_hash_table.hpp:513 ↗
allocator_t get_allocator() const noexcept;

See also

probe_to_findProbe optimized for find: checks if a value is present.
basic_hash_table.hpp:528 ↗
template <typename comparable_key_type_, typename callback_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
void probe_to_find(
    comparable_key_type_ && wanted,
    callback_type_ && call,
    tags_types_...) const noexcept;
template <typename comparable_key_type_, typename callback_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
void probe_to_find(
    comparable_key_type_ && wanted,
    callback_type_ && call,
    tags_types_...) noexcept;

Unlike probe_to_insert or probe_to_upsert, doesn't track "deleted" slots.

Parameters

wantedinHashable and comparable with key object.
callinA callback receiving a const_slot_ref_t to the matching slot's object.
probe_to_visitVisits every populated slot matching wanted, not just the first.
basic_hash_table.hpp:549 ↗
template <typename comparable_key_type_, typename callback_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
void probe_to_visit(
    comparable_key_type_ && wanted,
    callback_type_ && call,
    tags_types_...) const noexcept;

A table whose equality separates versions of one key files them all in one probe run, because the hasher still peels to the bare key. Finding one of them is not finding the newest one.

Parameters

wantedinHashable and comparable with key object.
callinReceives each const_slot_ref_t and returns walk_control_t. Must be noexcept.
probe_to_insertProbe optimized for insert of a unique key.
basic_hash_table.hpp:589 ↗
template <typename comparable_key_type_, typename callback_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
upsert_result_t probe_to_insert(
    comparable_key_type_ && wanted,
    callback_type_ && call,
    tags_types_...) noexcept;

Unlike probe_to_upsert avoids potentially expensive equality comparisons, knowing that the incoming key is different from all present members.

Parameters

wantedinHashable and comparable with key object.
callinA callback receiving a slot_ref_t to UN-initialized memory, where an element should be built.

Returns

Whether a slot took the element; the counters only move when one did.

probe_to_upsertProbe optimized for potential upserts.
basic_hash_table.hpp:635 ↗
template <typename comparable_key_type_, typename callback_unused_type_, typename callback_equal_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
upsert_result_t probe_to_upsert(
    comparable_key_type_ && wanted,
    callback_unused_type_ && call_unused,
    callback_equal_type_ && call_equal,
    tags_types_...) noexcept;

More expensive than probe_to_find and probe_to_insert, so pick this method wisely.

Parameters

wantedinHashable and comparable with key object.
call_unusedinA callback receiving a slot_ref_t to UN-initialized memory, where an element should be built.
call_equalinA callback receiving a slot_ref_t to the matching slot's object.

Returns

What the probe managed to do.

Statuses

unsafe_retargetRetargets an element reference to a specific slot using direct array indexing.
basic_hash_table.hpp:657 ↗
template <typename element_ref_type_>
void unsafe_retarget(
    element_ref_type_ & slot,
    offset_t slot_index) const noexcept;

Parameters

slotoutSlot reference to retarget with new region pointers and a new index.
slot_indexinAbsolute slot index in [0, slots_count).
slots_remaining_afterNumber of slots between the reference's position and the end of the table.
basic_hash_table.hpp:667 ↗
template <typename address_or_iterator_type_>
offset_t slots_remaining_after(
    address_or_iterator_type_ && slot) const noexcept;
containsReports whether a key equivalent to wanted is present.
basic_hash_table.hpp:678 ↗
template <typename comparable_key_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
expected<bool> contains(
    comparable_key_type_ && wanted,
    tags_types_... tags) const noexcept;

Parameters

wantedinKey to probe for.
tagsinDispatch tags the probe understands.

Returns

Whether the key is present; a probe over owned slots never allocates or refuses.

findSimilar to STL, searches for the element with the given key.
basic_hash_table.hpp:692 ↗
template <typename comparable_key_type_ = key_t const &>
const_iterator_t find(comparable_key_type_ && wanted) const noexcept;
template <typename comparable_key_type_ = key_t const &>
iterator_t find(comparable_key_type_ && wanted) noexcept;

Returns

End iterator, iff element with such key is missing.

atReturns a reference to the mapped value of the element with the given key.
basic_hash_table.hpp:724 ↗
template <typename comparable_key_type_ = key_t const &>
mapped_storage_t & at(comparable_key_type_ && key) noexcept;
template <typename comparable_key_type_ = key_t const &>
mapped_storage_t const & at(comparable_key_type_ && key) const noexcept;

Returns a const reference to the mapped value of the element with the given key.

See also

Warnings

A missing key has no value to name and this library does not throw, so the call aborts rather than hand back a reference one past the values region.

operator[]A temporarily banned function, that is replaced by at().
basic_hash_table.hpp:764 ↗
template <typename comparable_key_type_ = key_t const &>
void operator[](comparable_key_type_ &&)=delete;

See also

countNumber of elements matching the key, either 1 or 0 as duplicates aren't allowed.
basic_hash_table.hpp:771 ↗
template <typename comparable_key_type_ = key_t const &>
expected<std::size_t> count(comparable_key_type_ && key) const noexcept;

See also

for_eachInvokes the callback for every populated slot, bucket by bucket, until it halts the walk.
basic_hash_table.hpp:815 ↗
template <typename callback_type_ = no_op<slot_ref_t>>
status_t for_each(callback_type_ && callback) noexcept;
template <typename callback_type_ = no_op<const_slot_ref_t>>
status_t for_each(callback_type_ && callback) const noexcept;
emplace_hintDeleted: hint-based emplace is not supported.
basic_hash_table.hpp:868 ↗
template <typename... args_types_>
iterator_t emplace_hint(
    const_iterator_t,
    args_types_ && ...) noexcept=delete;

A hint names a position, and an open-addressed table decides position by hashing the key, so there is nothing a caller could usefully hint at. Declaring it deleted rather than omitting it turns a port from std::unordered_map into a signposted error instead of an unexplained missing member. Use emplace, or probe_to_upsert for finer control.

See also

emplaceMain insertion method for maps, finding the optimal location and overwriting it.
basic_hash_table.hpp:884 ↗
template <emplace_report_t report_ = emplace_report_t::no_result_k, typename convertible_key_type_, typename convertible_mapped_type_, typename... tags_types_, typename std::enable_if<can_use_map_emplace<convertible_key_type_, convertible_mapped_type_>(), int>::type = 0>
requires (promises_something<tags_types_> && ...)
std::conditional_t<report_==emplace_report_t::insert_result_k, insert_result_t, void> emplace(
    convertible_key_type_ && key,
    convertible_mapped_type_ && value,
    tags_types_... tags) noexcept;
template <emplace_report_t report_ = emplace_report_t::no_result_k, typename convertible_key_type_, typename... tags_types_, typename std::enable_if<can_use_set_emplace<convertible_key_type_>(), int>::type = 0>
requires (promises_something<tags_types_> && ...)
std::conditional_t<report_==emplace_report_t::insert_result_k, insert_result_t, void> emplace(
    convertible_key_type_ && key,
    tags_types_... tags) noexcept;

Main insertion method for sets, finding the optimal location and overwriting it.

More performant than insert, as it avoids temporary object construction.

Parameters

keyinThe key to insert.
valueinThe mapped value to store under key.
tagsinMarkers for special acceleration: assume_reserved_t avoids reserving more memory, assume_unique_t skips equality comparisons.

See also

insertMain insertion and upsertion method, compatible with STL code.
basic_hash_table.hpp:999 ↗
template <emplace_report_t report_ = emplace_report_t::no_result_k, typename temporary_pack_type_, typename... tags_types_>
requires (!readable_iterator<std::remove_cvref_t<temporary_pack_type_>> && (promises_something<tags_types_> && ...))
std::conditional_t<report_==emplace_report_t::insert_result_k, insert_result_t, void> insert(
    temporary_pack_type_ && pack,
    tags_types_... tags) noexcept;
template <multipass_iterator begin_iterator_type_, iterator_sentinel<begin_iterator_type_> end_iterator_type_, typename... tags_types_>
requires (promises_about_the_room<tags_types_> && ...)
status_t insert(
    begin_iterator_type_ begin,
    end_iterator_type_ end,
    tags_types_... tags) noexcept;

Inserts every element of [ begin, end ), refusing the batch when a key is already present here.

All-or-nothing over this table from the first element on, a taken key included: every key is checked against the table before the first of them is written.

See also

Notes

With maps this expects a mapping, exposing key and mapped members.

Statuses

upsertWrites every element of [ begin, end ), replacing the keys already here.
basic_hash_table.hpp:1029 ↗
template <multipass_iterator begin_iterator_type_, iterator_sentinel<begin_iterator_type_> end_iterator_type_, typename... tags_types_>
requires (promises_about_the_room<tags_types_> && ...)
status_t upsert(
    begin_iterator_type_ begin,
    end_iterator_type_ end,
    tags_types_... tags) noexcept;
template <typename convertible_element_type_>
status_t upsert(convertible_element_type_ && element) noexcept;

Files an element under its own key, replacing whatever key compared equal to it.

All-or-nothing over this table from the first element on. Growth is secured before any slot is written, and after it a bounded probe cannot run out: reserve_more leaves at least a quarter of the slots free, and both probes walk every slot before giving up. An element that can refuse its own copy is duplicated in full before the first of them lands, because a slot once written cannot be un-written without leaving a tombstone that moves later probes.

Parameters

elementinElement to file, moved into the table.

Statuses

insert_if_missingInserts every element of [ begin, end ) whose key is free, leaving each incumbent key alone.
basic_hash_table.hpp:1048 ↗
template <multipass_iterator begin_iterator_type_, iterator_sentinel<begin_iterator_type_> end_iterator_type_, typename... tags_types_>
requires (promises_about_the_room<tags_types_> && ...)
status_t insert_if_missing(
    begin_iterator_type_ begin,
    end_iterator_type_ end,
    tags_types_... tags) noexcept;

All-or-nothing over this table from the first element on, on the same terms as upsert.

Statuses

mergeAn interface similar to std::unordered_map::merge, banned in favor of a manual insert(begin, end).
basic_hash_table.hpp:1131 ↗
template <typename other_type_>
void merge(other_type_ &&)=delete;

See also

eraseRemoves the element the reference points to, leaving a tombstone behind.
basic_hash_table.hpp:1206 ↗
void erase(slot_ref_t slot) noexcept;
void erase(iterator_t slot) noexcept;
void erase(const_iterator_t)=delete;
template <typename comparable_key_type_, typename... tags_types_>
requires (promises_something<tags_types_> && ...)
bool erase(comparable_key_type_ && key, tags_types_... tags) noexcept;

Removes a value by key in the most efficient fashion.

If you already have the iterator, use the faster overload that avoids the search entirely.

Parameters

keyinThe key to remove.
tagsinMarkers for special acceleration: assume_reserved_t assumes non-empty.

See also

Warnings

The reference must point at a populated slot.

Notes

Erasures never deallocate; memory stays cluttered until the next force_resize().

reserveGrows the table to fit that many elements, if the current capacity is short.
basic_hash_table.hpp:1271 ↗
reserve_result_t reserve(offset_t planned_elements) noexcept;

Returns

Whether the layout moved, invalidating older iterators.

See also

Statuses

  • failed_k The larger buffer could not be allocated, leaving this table untouched.
reserve_moreThe recommended entry point for bulk insertions, sized for what is present already.
basic_hash_table.hpp:1283 ↗
reserve_result_t reserve_more(offset_t new_elements) noexcept;

Returns

Whether the layout moved, invalidating older iterators.

Statuses

  • failed_k The larger buffer could not be allocated, leaving this table untouched.
force_resizeExports the data into a fresh table of the requested size and swaps the contents.
basic_hash_table.hpp:1295 ↗
reserve_result_t force_resize(hash_slots_count_t slots_count) noexcept;

Also used to clean the tombstones left behind by deletions.

Statuses

  • failed_k The request cannot hold what is stored, or the new buffer could not be allocated, leaving this table untouched either way.
swapA cheap copy-less swap mechanism used in move-constructors.
basic_hash_table.hpp:1307 ↗
void swap(basic_hash_table & other) noexcept;

See also

clearErases all elements, keeping the memory.
basic_hash_table.hpp:1317 ↗
void clear() noexcept;

For deallocation call shrink_to_fit().

See also

shrink_to_fitBrings the memory consumption of the container to the minimum.
basic_hash_table.hpp:1326 ↗
void shrink_to_fit() noexcept;

See also

Notes

Best-effort, matching the void shape of the standard containers: a refused allocation leaves the table exactly as it was, with every element still reachable. Call force_resize when the outcome has to be known.

deallocateDeallocates all the memory used by the container.
basic_hash_table.hpp:1337 ↗
void deallocate() noexcept;

Warnings

Call clear() first to destruct all the non-trivial elements.

rehashSets the number of buckets to the desired value, rehashing in the process.
basic_hash_table.hpp:1349 ↗
reserve_result_t rehash(offset_t count_buckets) noexcept;

Returns

Whether the layout moved, invalidating older iterators.

See also

Notes

A request too small for what is stored is raised to what the current size needs, so rehash(0) compacts rather than emptying the table into a layout with no slots at all.

Statuses

  • failed_k The new buffer could not be allocated, leaving this table untouched.
move_to_newCreates a new buffer of the requested capacity and rehashes the present data into it.
basic_hash_table.hpp:1365 ↗
basic_hash_table move_to_new(hash_slots_count_t slots_count) noexcept;

Move-constructors are used, avoiding copies, and this table is left deallocated.

Returns

An unallocated table when the allocator refused, nothing was asked for, or the request has fewer slots than this table has elements, leaving this one untouched.

copy_to_newDuplicates the table, reusing the exact bucket layout when the elements are trivial.
basic_hash_table.hpp:1390 ↗
basic_hash_table copy_to_new() const noexcept;
basic_hash_table copy_to_new(
    hash_slots_count_t slots_count) const noexcept;

Duplicates the table into a differently sized one, rehashing every element.

makeMost commonly used constructor-like interface, sized by the planned element count.
basic_hash_table.hpp:419 ↗
expected<basic_hash_table> make(
    offset_t planned_elements,
    hasher_t hasher = {},
    equals_t equals = {},
    allocator_t allocator = {}) noexcept;
expected<basic_hash_table> make(
    hash_slots_count_t slots,
    hasher_t hasher = {},
    equals_t equals = {},
    allocator_t allocator = {}) noexcept;

Most commonly used constructor-like interface, sized by the exact slot count.

template <multipass_iterator begin_iterator_type_, iterator_sentinel<begin_iterator_type_> end_iterator_type_>
expected<basic_hash_table> make(
    begin_iterator_type_ begin,
    end_iterator_type_ end,
    hasher_t hasher = {},
    equals_t equals = {},
    allocator_t allocator = {}) noexcept;

Builds a table pre-sized for [ begin, end ) and fills it.

A refusal leaves nothing allocated: the table is new and empty, so there is no earlier state for a half-filled one to be told apart from.

Statuses

memory_usageTotal memory needed for the three regions of a table this size.
basic_hash_table.hpp:1259 ↗
std::size_t memory_usage(hash_slots_count_t slots_count) noexcept;

Parameters

slots_countinTotal number of slots, a power of two and a multiple of 32.

Returns

Total bytes needed for the single allocation containing all three regions.

insert_result_tA return type for the insert function, similar to insert_return_type in STL.
basic_hash_table.hpp:246 ↗
struct insert_result_t

position names the stored or the conflicting element, and the end when nothing was stored. outcome says which of the four happened, so a key already taken, a probe with no slot left and an allocation the table could not make are all distinguishable from one another, without comparing the iterator against end().

Fields

positioniterator_t
outcomeupsert_result_t
Classbasic_ringA first-in first-out ring of at most capacity elements of value_type_, which a failed push reports.16
basic_ring.hpp:35 ↗
template <typename value_type_,
          allocator_of<value_type_> allocator_type_ = default_allocator<value_type_>>
class basic_ring
capacity_limit_kThe largest capacity, so a size always fits the 32-bit counters.
basic_ring.hpp:44 ↗
std::size_t capacity_limit_k;
basic_ring
basic_ring.hpp:73 ↗
basic_ring() noexcept=default;
basic_ring(allocator_t allocator) noexcept;
basic_ring(basic_ring && other) noexcept;
basic_ring(basic_ring const &)=delete;
~basic_ring
basic_ring.hpp:77 ↗
~basic_ring() noexcept;
operator=
basic_ring.hpp:84 ↗
basic_ring & operator=(basic_ring && other) noexcept;
basic_ring & operator=(basic_ring const &)=delete;
oldestThe element the next pop removes; the ring must not be empty.
basic_ring.hpp:123 ↗
value_t & oldest() noexcept;
value_t const & oldest() const noexcept;
newestThe element the last push added; the ring must not be empty.
basic_ring.hpp:133 ↗
value_t & newest() noexcept;
value_t const & newest() const noexcept;
operator[]The element index places after the oldest, without bounds checking.
basic_ring.hpp:143 ↗
value_t & operator[](std::size_t index) noexcept;
value_t const & operator[](std::size_t index) const noexcept;
atThe element index places after the oldest, or null past the newest.
basic_ring.hpp:153 ↗
value_t * at(std::size_t index) noexcept;
value_t const * at(std::size_t index) const noexcept;
pushAppends element to a ring known not to be full.
basic_ring.hpp:161 ↗
status_t push(assume_reserved_t, value_t && element) noexcept;
status_t push(value_t && element) noexcept;

Appends element, or answers capacity_exhausted_k and leaves the ring alone.

push_evictingAppends element, first moving the oldest into evicted when the ring is full.
basic_ring.hpp:176 ↗
ring_eviction_t push_evicting(
    value_t && element,
    value_t & evicted) noexcept;

Capacity must be nonzero.

popDestroys the oldest element; the ring must not be empty.
basic_ring.hpp:189 ↗
void pop() noexcept;
pop_intoMoves the oldest element into destination and removes it, or answers operation_would_block_k.
basic_ring.hpp:197 ↗
status_t pop_into(value_t & destination) noexcept;
push_manyCopies as many of elements as fit, oldest first, and returns how many.
basic_ring.hpp:205 ↗
requires std::is_nothrow_copy_constructible_v<value_t>
std::size_t push_many(std::span<value_t const> elements) noexcept;
pop_manyMoves as many elements as destination holds out of the ring, oldest first, and returns how many.
basic_ring.hpp:218 ↗
std::size_t pop_many(std::span<value_t> destination) noexcept;
clearDestroys every element and keeps the slots.
basic_ring.hpp:228 ↗
void clear() noexcept;
makeAn empty ring of capacity slots, where zero allocates nothing and holds nothing.
basic_ring.hpp:105 ↗
expected<basic_ring> make(
    std::size_t capacity,
    allocator_t allocator = {}) noexcept;

Returns

The ring.

Statuses

Classbasic_vectorException-free dynamic array that uses error codes instead of exceptions.27
basic_vector.hpp:62 ↗
template <typename value_type_,
          allocator_of<value_type_> allocator_type_ = default_allocator<value_type_>>
class basic_vector

Provides RAII memory management with explicit failure handling, unlike std::vector.

Template Requirements

  • value_type_ must be nothrow move-constructible, and nothrow default-constructible to be resized.
  • For types with potentially throwing constructors, provide a static .make() method returning expected<value_type_> to enable exception-free construction via emplace_back().

See also

basic_vectorDefault constructor creates an empty vector.
basic_vector.hpp:86 ↗
basic_vector() noexcept=default;
basic_vector(allocator_t allocator) noexcept;

Constructor with custom allocator.

basic_vector(basic_vector && other) noexcept;

Move constructor transfers ownership.

basic_vector(basic_vector const &)=delete;

Copy construction is deleted (use .copy() method for explicit copies).

Parameters

allocatorinThe allocator instance to use.
~basic_vectorDestructor deallocates memory and destroys all elements.
basic_vector.hpp:95 ↗
~basic_vector() noexcept;
operator=Move assignment transfers ownership after destroying current elements.
basic_vector.hpp:108 ↗
basic_vector & operator=(basic_vector && other) noexcept;
basic_vector & operator=(basic_vector const &)=delete;

Copy assignment is deleted (use .copy() method for explicit copies).

copyCreates a deep copy of this vector.
basic_vector.hpp:157 ↗
expected<basic_vector> copy() const noexcept;

Returns

expected<basic_vector> containing the copy, or error status.

reserveReserves capacity for at least new_capacity elements.
basic_vector.hpp:185 ↗
status_t reserve(std::size_t new_capacity) noexcept;

Grows by at least a doubling, so asking for one more slot at a time still amortizes to constant cost.

Parameters

new_capacityinThe new capacity.

Statuses

push_backAppends an element without capacity check (performance variant).
basic_vector.hpp:222 ↗
status_t push_back(assume_reserved_t, value_t && value) noexcept;
status_t push_back(value_t && value) noexcept;

Appends an element to the end of the vector.

Requires pre-reserved capacity. Use for hot paths after reserve().

Tag indicating capacity was pre-reserved.

Parameters

valueinElement to append (moved into the vector).

Returns

Always returns success for noexcept move construction.

pop_backRemoves the last element from the vector.
basic_vector.hpp:243 ↗
void pop_back() noexcept;

Precondition: Vector must not be empty.

insertInserts value at position into reserved room, shifting the tail up by one.
basic_vector.hpp:254 ↗
status_t insert(
    assume_reserved_t,
    std::size_t position,
    value_t && value) noexcept;
status_t insert(std::size_t position, value_t && value) noexcept;

Inserts value at position, shifting the elements at and after it up by one.

Linear in the elements after position, which is what a sorted run of a few hundred entries costs and why an ordered container built on this stays small.

Parameters

positioninIndex the new element takes, at most size().
valueinElement to insert (moved into the vector).

Returns

Always success, since nothing here can refuse.

eraseRemoves position's element, handing it to callback, then shifts the rest down.
basic_vector.hpp:289 ↗
template <typename callback_type_ = no_op_t>
requires std::invocable<callback_type_ &, value_t &>
void erase(
    std::size_t position,
    callback_type_ && callback = {}) noexcept;
template <typename callback_type_ = no_op_t>
requires std::invocable<callback_type_ &, value_t &>
void erase(
    std::size_t position,
    std::size_t count,
    callback_type_ && callback = {}) noexcept;

Removes count elements from position, handing each to callback first and shifting the rest down.

Parameters

positioninIndex of the element to remove, below size().
callbackinReceives the element before it is moved away. Must be noexcept.
countinElements to remove, which must not run past the end.
erase_ifRemoves every element predicate admits in one pass, keeping the order of the rest.
basic_vector.hpp:325 ↗
template <typename predicate_type_, typename callback_type_ = no_op_t>
requires std::predicate<predicate_type_ &, value_t &> && std::invocable<callback_type_ &, value_t &>
std::size_t erase_if(
    predicate_type_ && predicate,
    callback_type_ && callback = {}) noexcept;

Parameters

predicateinDecides which elements go. Must be noexcept.
callbackinReceives each removed element before it moves away. Must be noexcept.

Returns

How many elements were removed.

emplace_backConstructs element in-place without capacity check (performance variant).
basic_vector.hpp:352 ↗
template <typename... args_types_>
status_t emplace_back(
    assume_reserved_t,
    args_types_ &&... args) noexcept;
template <typename... args_types_>
status_t emplace_back(args_types_ &&... args) noexcept;

Constructs element in-place at the end of the vector.

Requires pre-reserved capacity. Use for hot paths after reserve().

Tag indicating capacity was pre-reserved.

Parameters

argsinArguments to forward to element constructor.

Returns

Success, or error from .make() method if construction can throw.

Notes

For types with potentially throwing constructors, provide a static .make() method returning expected<value_t> to enable exception-free construction.

resizeResizes the vector to contain new_size elements.
basic_vector.hpp:407 ↗
status_t resize(std::size_t new_size) noexcept;
requires std::is_trivially_copyable_v<value_t>
status_t resize(assume_uninitialized_t, std::size_t new_size) noexcept;

Resizes the vector to new_size without constructing the new elements, so their first writer can be a kernel or a byte copy rather than the host.

status_t resize(std::size_t new_size, value_t const & value) noexcept;

Resizes the vector and initializes new elements with value.

If new_size < size(), elements are destroyed. If new_size > size(), new elements are default-constructed.

Parameters

new_sizeinThe new size.
valueinValue to copy into new elements.

Returns

Success, or error code on failure. On failure the elements are unchanged, though the capacity may already have grown.

assignReplaces the contents with a copy of values, all or nothing.
basic_vector.hpp:486 ↗
status_t assign(std::span<value_t const> values) noexcept;

Parameters

valuesinElements to copy, never ones of this vector's own.

Returns

Success, or why the room or a copy could not be made, which changes nothing.

clearDestroys all elements but keeps allocated capacity.
basic_vector.hpp:509 ↗
void clear() noexcept;
swapSwaps contents with another vector.
basic_vector.hpp:526 ↗
status_t swap(basic_vector & other) noexcept;

Parameters

otherinoutThe vector to swap with.

Notes

If propagate_on_container_swap is false (e.g., std::allocator), allocators must compare equal. Attempting to swap vectors with unequal non-propagating allocators returns invalid_argument_k and leaves both vectors unchanged.

Statuses

operator[]Accesses element at index without bounds checking.
basic_vector.hpp:549 ↗
value_t & operator[](std::size_t index) noexcept;
value_t const & operator[](std::size_t index) const noexcept;

Accesses element at index without bounds checking (const version).

Parameters

indexinThe element index.

Returns

Reference to the element.

atAccesses element at index with bounds checking.
basic_vector.hpp:569 ↗
value_t * at(std::size_t index) noexcept;
value_t const * at(std::size_t index) const noexcept;

Accesses element at index with bounds checking (const version).

Parameters

indexinThe element index.

Returns

Pointer to the element, or nullptr if out of bounds.

frontAccesses the first element.
basic_vector.hpp:582 ↗
value_t & front() noexcept;
value_t const & front() const noexcept;

Accesses the first element (const version).

Returns

Reference to the first element.

backAccesses the last element.
basic_vector.hpp:600 ↗
value_t & back() noexcept;
value_t const & back() const noexcept;

Accesses the last element (const version).

Returns

Reference to the last element.

dataReturns pointer to underlying array.
basic_vector.hpp:618 ↗
value_t * data() noexcept;
value_t const * data() const noexcept;

Returns pointer to underlying array (const version).

Returns

Pointer to the underlying element storage.

emptyChecks whether the vector is empty.
basic_vector.hpp:634 ↗
bool empty() const noexcept;

Returns

true if the vector is empty, false otherwise.

sizeReturns the number of elements.
basic_vector.hpp:640 ↗
std::size_t size() const noexcept;

Returns

The number of elements in the vector.

capacityReturns the capacity.
basic_vector.hpp:646 ↗
std::size_t capacity() const noexcept;

Returns

The number of elements that can be held in currently allocated storage.

get_allocatorA copy of this vector's allocator, for building a sibling that allocates the same way.
basic_vector.hpp:649 ↗
allocator_t get_allocator() const noexcept;
beginReturns an iterator to the beginning.
basic_vector.hpp:656 ↗
value_t * begin() noexcept;
value_t const * begin() const noexcept;

Returns a const iterator to the beginning.

endReturns an iterator to the end.
basic_vector.hpp:659 ↗
value_t * end() noexcept;
value_t const * end() const noexcept;

Returns a const iterator to the end.

makeCreates a new vector with specified capacity.
basic_vector.hpp:134 ↗
expected<basic_vector> make(std::size_t initial_capacity) noexcept;
expected<basic_vector> make(
    std::size_t initial_capacity,
    allocator_t allocator) noexcept;

Creates a new vector with specified capacity and allocator.

Parameters

initial_capacityinInitial capacity to allocate.
allocatorinThe allocator instance to use.

Returns

expected<basic_vector> containing the new vector, or error status.

Classbasic_wb_nodeNode for weight-balanced binary search tree.36
basic_wb_tree.hpp:104 ↗
template <typename value_type_,
          typename comparator_type_,
          typename augmentation_type_ = no_augmentation_t>
class basic_wb_node

Stores subtree size for O(log n) order statistics and O(1) amortized rebalancing.

Rebalancing Parameters

Δ = 3 is the rotation threshold: rebalance when weight(left) > 3 × weight(right) or vice versa, with weight = size + 1. Γ = 2 selects the rotation type: a single rotation when weight(heavy.inner) < 2 × weight(heavy.outer).

These are the only valid integer parameters, proven in Coq by Hirai and Yamamoto in 2011, and are exposed as delta_k and gamma_k.

Layout: payload is the stored entry, left and right the links, and size the number of nodes in the subtree rooted here - invariant size = 1 + size(left) + size(right), which is what makes select and rank logarithmic.

Augmentation

An augmented tree keeps augmented_size next to size, holding the number of entries in the subtree for which augmentation_type_::augmented_count returns 1. Because that count is read off the entry, every rotation, join, and split restores it from the moved node's own entry and its two children. An entry whose count flips while it sits in the tree is repaired by refresh_augmentation, which rewalks only the root-to-entry path and may be called any number of times per write - one wrapper operation is free to retag several entries.

node_placement_tWhat a walk that may create a node did with the key it was handed.
basic_wb_tree.hpp:688 ↗
enum node_placement_t

Values

refused_kNo node was available to store the key, so nothing was stored.
matched_kAn equal key was already there, and match names the node holding it.
made_kA fresh node took the key, and match names it.
augmented_count_tThe augmented counter, degenerating to an empty type when no augmentation is asked for.
basic_wb_tree.hpp:123 ↗
using augmented_count_t = std::conditional_t<is_augmented_k, std::size_t, no_augmented_count_t>;
is_augmented_kTrue when a second count is maintained, which is what gates every augmented code path.
basic_wb_tree.hpp:118 ↗
bool is_augmented_k;
get_augmented_sizeNumber of entries in the subtree rooted at node that the policy counts.
basic_wb_tree.hpp:139 ↗
size_t get_augmented_size(node_t *node) noexcept;
get_own_augmented_countThe entry's own contribution to the augmented count, 0 or 1.
basic_wb_tree.hpp:145 ↗
size_t get_own_augmented_count(node_t *node) noexcept;
find_minFind minimum (leftmost) node in subtree.
basic_wb_tree.hpp:157 ↗
node_t * find_min(node_t *node) noexcept;

Parameters

nodeinRoot of subtree.

Returns

Pointer to minimum node, or nullptr if tree is empty.

find_maxFind maximum (rightmost) node in subtree.
basic_wb_tree.hpp:168 ↗
node_t * find_max(node_t *node) noexcept;

Parameters

nodeinRoot of subtree.

Returns

Pointer to maximum node, or nullptr if tree is empty.

find_successorFinds the next node in in-order traversal (successor).
basic_wb_tree.hpp:181 ↗
node_t * find_successor(
    node_t *root,
    node_t *node,
    comparator_t const & comparator) noexcept;

Parameters

rootinRoot of the tree.
nodeinCurrent node.
comparatorinComparator for ordering.

Returns

The next node in order, or nullptr when there is none.

find_predecessorFinds the previous node in in-order traversal (predecessor).
basic_wb_tree.hpp:193 ↗
node_t * find_predecessor(
    node_t *root,
    node_t *node,
    comparator_t const & comparator) noexcept;

Parameters

rootinRoot of the tree.
nodeinCurrent node.
comparatorinComparator for ordering.

Returns

The previous node in order, or nullptr when there is none.

selectSelect k-th smallest element (0-indexed).
basic_wb_tree.hpp:232 ↗
node_t * select(
    node_t *node,
    size_t k,
    comparator_t const & comparator) noexcept;

Runs in O(log n) expected time, where n = size(node).

Finding the median and the first quartile:

auto median = select(root, size/2, comp);  // Find median
auto q1 = select(root, size/4, comp);      // First quartile

Parameters

nodeinRoot of subtree.
kinIndex of element to find (0 = minimum, size-1 = maximum).
comparatorinComparator instance.

Returns

Pointer to k-th node, or nullptr if k >= size.

rankFind rank (position) of element in sorted order.
basic_wb_tree.hpp:258 ↗
template <typename comparable_type_>
size_t rank(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Runs in O(log n) expected time. Counting the elements ordered before value:

auto position = rank(root, value, comp);
// position elements are smaller than value
// If value exists, it's at index position

Parameters

nodeinRoot of subtree.
comparableinKey to find rank of.
comparatorinComparator instance.

Returns

Number of elements < comparable. If element exists, this is its 0-based index.

select_augmentedSelect the k-th smallest entry among those the augmentation policy counts.
basic_wb_tree.hpp:284 ↗
node_t * select_augmented(node_t *node, size_t k) noexcept;

Runs in O(log n) time: one root-to-node path, reading only the stored counts.

Parameters

nodeinRoot of subtree.
kinIndex among counted entries, 0 for the smallest.

Returns

Pointer to the k-th counted node, or nullptr when fewer than k+1 are counted.

rank_augmentedNumber of counted entries ordered before comparable.
basic_wb_tree.hpp:310 ↗
template <typename comparable_type_>
size_t rank_augmented(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Runs in O(log n) time.

Parameters

nodeinRoot of subtree.
comparableinKey to find the augmented rank of.
comparatorinComparator instance.
refresh_augmentationRepairs augmented counts on the path to comparable after its predicate flipped.
basic_wb_tree.hpp:331 ↗
template <typename comparable_type_>
bool refresh_augmentation(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Runs in O(log n) time, touching only the ancestors whose counts could have moved.

Returns

True when the entry was found, which is when anything was repaired.

for_each_left_rightIn-order traversal (left-root-right) with callback.
basic_wb_tree.hpp:355 ↗
template <typename callback_type_>
walk_control_t for_each_left_right(
    node_t *node,
    callback_type_ && callback) noexcept;

Visits nodes in sorted order according to comparator.

Parameters

nodeinRoot of subtree to traverse.
callbackinCallback to invoke for each node. Must be noexcept.

Returns

Whether the tree ran out or a halting callback stopped the walk first.

rangeFinds all nodes in the half-open range [low, high) and invokes callback for each.
basic_wb_tree.hpp:376 ↗
template <typename lower_type_, typename upper_type_, typename callback_type_>
walk_control_t range(
    node_t *node,
    lower_type_ && low,
    upper_type_ && high,
    comparator_t const & comparator,
    callback_type_ && callback) noexcept;

Recursively traverses the tree, visiting only nodes within the specified range.

Parameters

nodeinRoot of subtree to search.
lowinLower bound of range (inclusive).
highinUpper bound of range (exclusive).
comparatorinComparator for element comparison.
callbackinCallback to invoke for each node in range. Must be noexcept.

Returns

Whether the range ran out or a halting callback stopped the walk first.

Warnings

Current recursive implementation is suboptimal.

update_sizeUpdate size field to match children.
basic_wb_tree.hpp:401 ↗
void update_size(node_t *node) noexcept;

Must be called after any operation that modifies children.

update_augmented_sizeRecomputes only the augmented count, for a node whose shape is already correct.
basic_wb_tree.hpp:409 ↗
void update_augmented_size(node_t *node) noexcept;

Used where an entry's own predicate changed but no link moved.

reset_to_leafRestores a node whose links were just cleared, so both counts describe it alone.
basic_wb_tree.hpp:418 ↗
void reset_to_leaf(node_t *node) noexcept;
build_from_sortedBuilds a balanced tree from sorted range [ first, first + count ) in O(n).
basic_wb_tree.hpp:454 ↗
template <multipass_iterator iterator_type_, allocator_of<node_t> node_allocator_type_>
build_result_t build_from_sorted(
    iterator_type_ first,
    std::size_t count,
    node_allocator_type_ & allocator) noexcept;

Parameters

firstinBeginning of the sorted range.
countinNumber of elements in the range.
allocatorinoutHands back a raw node, or nullptr when it cannot.

Returns

What was built.

Warnings

An unsorted range builds a tree whose structure is undefined.

Notes

A range handing out rvalues, as a move iterator does, gives its elements up.

Statuses

  • Any status an element's copy returns passes through unchanged.
rotate_rightSingle right rotation.
basic_wb_tree.hpp:501 ↗
node_t * rotate_right(node_t *y) noexcept;
*      y              x
*     / \            / \
*    x   C    =>    A   y
*   / \                / \
*  A   B              B   C
*  
rotate_leftSingle left rotation.
basic_wb_tree.hpp:525 ↗
node_t * rotate_left(node_t *x) noexcept;
*    x                y
*   / \              / \
*  A   y      =>    x   C
*     / \          / \
*    B   C        A   B
*  
get_weightWeight of a subtree, counting the empty tree as 1.
basic_wb_tree.hpp:545 ↗
size_t get_weight(node_t *node) noexcept;

Hirai and Yamamoto state the invariant over size+1; comparing raw sizes makes every node with an empty child look unbalanced and asks for rotations that cannot be performed, since the pivot's child is null.

is_balancedCheck if node satisfies weight-balance invariant.
basic_wb_tree.hpp:551 ↗
bool is_balanced(node_t *node) noexcept;

Returns

True when weight(left) ≤ Δ × weight(right) and weight(right) ≤ Δ × weight(left).

rebalanceRebalances a node whose weight invariant is violated.
basic_wb_tree.hpp:568 ↗
node_t * rebalance(node_t *node) noexcept;

Uses Δ = 3 and Γ = 2, the only valid integer solution.

When the left side is too heavy, weight(left) > 3 × weight(right), a single right rotation fixes it if weight(left.right) < 2 × weight(left.left), and a double left-right rotation does otherwise. The right-heavy case mirrors this.

Returns

New root after rebalancing (may be unchanged).

extractPops the root replacing it with one of descendants, if present.
basic_wb_tree.hpp:618 ↗
extract_result_t extract(
    node_t *node,
    comparator_t const & comparator) noexcept;
template <typename comparable_type_>
extract_result_t extract(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Searches for a matching ancestor and extracts it out.

Parameters

nodeinNode to extract.
comparatorinComparator for element comparison.
comparableinAny key comparable with stored entries.
insertInserts an existing node into the tree.
basic_wb_tree.hpp:716 ↗
find_or_make_result_t insert(
    node_t *node,
    node_t *new_child,
    comparator_t const & comparator) noexcept;

Used by merge to insert extracted nodes.

Parameters

nodeinRoot of subtree to insert into.
new_childinPre-allocated node to insert.
comparatorinComparator for element comparison.

Returns

The new root, the node the key lives in, and whether it was made or matched.

upsertInserts or updates an element in the tree.
basic_wb_tree.hpp:764 ↗
template <allocator_of<node_t> node_allocator_type_>
find_or_make_result_t upsert(
    node_t *node,
    value_type_ && payload,
    comparator_t const & comparator,
    node_allocator_type_ & allocator) noexcept;

If key exists, overwrites the element. If not, creates new node.

Parameters

nodeinRoot of subtree.
payloadinEntry to insert or assign (moved).
comparatorinComparator for element comparison.
allocatorinoutHands back a raw node, or nullptr when it cannot.

Returns

The new root, the node the key lives in, and whether it was made or matched.

for_each_bottom_upVisits every node bottom-up, so a caller may free each one as it goes.
basic_wb_tree.hpp:809 ↗
template <typename callback_type_>
void for_each_bottom_up(
    node_t *node,
    callback_type_ && callback) noexcept;
erase_ifDrops every node satisfying predicate, returning the new root and surviving count.
basic_wb_tree.hpp:826 ↗
template <typename predicate_type_, typename node_deallocator_type_>
requires std::predicate<predicate_type_ &, value_t &>
erase_if_result_t erase_if(
    node_t *node,
    predicate_type_ && predicate,
    node_deallocator_type_ && node_deallocator,
    comparator_t const & comparator) noexcept;

Both halves may shed any number of nodes, so the two survivors are re-joined rather than stitched back in place - rotating once could not close a gap of many weight classes.

sample_rangeUniformly samples one node whose key lies in [lower, upper), or nullptr when empty.
basic_wb_tree.hpp:849 ↗
template <typename lower_type_, typename upper_type_, typename generator_type_, typename predicate_type_ = no_op_t>
requires uniform_random_bits<std::remove_cvref_t<generator_type_>> && (std::same_as<std::remove_cvref_t<predicate_type_>, no_op_t> || std::predicate<predicate_type_ &, node_t *>)
node_t * sample_range(
    node_t *node,
    lower_type_ && lower,
    upper_type_ && upper,
    comparator_t const & comparator,
    generator_type_ && generator,
    predicate_type_ && predicate = {}) noexcept;
join_with_rootJoins two trees with a root node between them.
basic_wb_tree.hpp:893 ↗
node_t * join_with_root(
    node_t *left,
    node_t *root,
    node_t *right) noexcept;

Precondition: root is non-null and all(left) < root < all(right).

The two sides may sit many weight classes apart, so hanging root between them and rotating once is not enough. Walking down the heavier side's inner spine until the two pieces are within Δ of each other, then rebalancing on the way back up, is what keeps the invariant - this is what makes split and erase_range produce balanced trees. Runs in O(log n) expected time, where n is the size of the larger tree.

Parameters

leftinLeft subtree (all elements < root).
rootinMiddle node to join with.
rightinRight subtree (all elements > root).

Returns

New root of joined tree.

joinJoins two trees where all(left) < all(right).
basic_wb_tree.hpp:928 ↗
node_t * join(
    node_t *left,
    node_t *right,
    comparator_t const & comparator) noexcept;

Recursively joins trees by extracting min from right subtree.

Runs in O(log n) expected time.

Parameters

leftinLeft subtree.
rightinRight subtree.
comparatorinComparator for ordering elements.

Returns

New root of joined tree.

splitSplits tree at a given key.
basic_wb_tree.hpp:957 ↗
template <typename comparable_type_>
split_result_t split(
    node_t *node,
    comparable_type_ && comparable,
    comparator_t const & comparator) noexcept;

Returns two trees: left contains all elements < comparable, right contains all elements >= comparable.

Runs in O(log n) expected time. Splitting around 5:

auto [left, right] = split(root, 5, comp);
// left: all elements < 5
// right: all elements >= 5

Parameters

nodeinRoot of tree to split.
comparableinKey to split at.
comparatorinComparator for element comparison.

Returns

Split result with left and right subtrees.

build_result_tWhat a bulk build produced, and why it stopped where it did.
basic_wb_tree.hpp:429 ↗
struct build_result_t

Not an expected, which holds a value or a reason and never both: a refused build still owns every node it made, and whoever asked for it has to free them.

Fields

rootnode_t *Everything that was built, which the owner frees whether the build finished or not.
statusstatus_tWhy the build stopped: a refused node, a refused element copy, or success_k.
countstd::size_tHow many nodes hang off root; a partial build leaves this short of the range.
split_result_tResult of splitting a tree at a key.
basic_wb_tree.hpp:869 ↗
struct split_result_t

Fields

leftnode_t *Subtree with all elements ordered before the split key.
rightnode_t *Subtree with all elements not ordered before the split key.
Classbasic_wb_treeWeight-balanced tree container with order statistics support.51
basic_wb_tree.hpp:994 ↗
template <typename value_type_,
          typename comparator_type_ = less_t,
          typename node_allocator_type_ = std::allocator<basic_wb_node<value_type_, comparator_type_>>,
          typename augmentation_type_ = no_augmentation_t>
class basic_wb_tree

Provides ordered storage similar to std::set with additional O(log n) select/rank operations.

Order Statistics API

Beyond standard tree operations, provides:

  • select(k): Find k-th smallest element in O(log n)
  • rank(x): Find position of element x in O(log n)

Use cases: pagination, percentiles, quantiles, window functions.

key_tSFINAE to extract key_type for maps, or use value_type for sets.
basic_wb_tree.hpp:1010 ↗
using key_t = typename mapping_key_type_or_itself<value_t>::type;
mapped_tSFINAE to extract mapped_type for maps, or void for sets.
basic_wb_tree.hpp:1014 ↗
using mapped_t = typename mapped_value_type_or_void<value_t>::type;
is_associativeTrait to indicate this container uses iterator-based reads, not callbacks.
basic_wb_tree.hpp:1018 ↗
using is_associative = std::bool_constant<is_mapping<value_t>>;
rebindRebind this tree type to different element and comparator types.
basic_wb_tree.hpp:1028 ↗
template <typename other_value_type_, typename other_comparator_>
using rebind = basic_wb_tree<other_value_type_, other_comparator_, typename std::allocator_traits<node_allocator_type_>::template rebind_alloc<basic_wb_node<other_value_type_, other_comparator_, augmentation_type_>>, augmentation_type_>;

Follows STL allocator rebind pattern for type transformations.

basic_wb_treeDefault constructor.
basic_wb_tree.hpp:1041 ↗
basic_wb_tree() noexcept=default;
basic_wb_tree(allocator_t allocator) noexcept;
basic_wb_tree(comparator_t comparator, allocator_t allocator) noexcept;

Seeds both policies, which a stateful comparator needs to survive a rebind.

basic_wb_tree(wb_tree_t && other) noexcept;

Move constructor.

basic_wb_tree(wb_tree_t const &)=delete;

Creates empty tree.

operator=Move assignment operator.
basic_wb_tree.hpp:1055 ↗
wb_tree_t & operator=(wb_tree_t && other) noexcept;
wb_tree_t & operator=(wb_tree_t const &)=delete;
~basic_wb_treeDestructor.
basic_wb_tree.hpp:1064 ↗
~basic_wb_tree() noexcept;

Deallocates all nodes.

emptyChecks whether the tree is empty.
basic_wb_tree.hpp:1078 ↗
bool empty() const noexcept;
insertInserts an element only when its key is absent, leaving any incumbent alone.
basic_wb_tree.hpp:1107 ↗
upserted_node_t insert(value_t && payload) noexcept;
template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t insert(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;

Inserts [ first, last ), refusing the batch on any key already here.

All-or-nothing over this tree from the first element on, a taken key included: the whole range is staged and checked before the merge absorbing it relinks a single node.

Parameters

payloadinEntry to insert, moved into the tree only when a node is made for it.

Returns

The node the key lives in, and whether it was made or matched.

Statuses

  • refused_k The allocator refused the node, so nothing was stored.
  • key_already_exists_k A key repeats inside an unsorted range or is already in this tree.
  • Any status an element's copy returns passes through unchanged.
insert_or_assignAtomically inserts or updates an element.
basic_wb_tree.hpp:1129 ↗
template <typename comparable_type_>
upserted_node_t insert_or_assign(
    comparable_type_ && comparable) noexcept;

Overwrites the existing element if key exists. Matches std::map::insert_or_assign() semantics.

Parameters

comparableinEntry to insert or assign (moved into the tree).

Returns

The node the entry now lives in, and whether it was made or matched.

Statuses

  • refused_k The key is absent and the allocator refused its node, so nothing was stored.
upsertAlias for insert_or_assign().
basic_wb_tree.hpp:1145 ↗
template <typename comparable_type_>
upserted_node_t upsert(comparable_type_ && comparable) noexcept;
template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t upsert(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;

Writes every element of [ first, last ), overwriting the keys already here.

Atomically inserts or updates an element.

Parameters

comparableinEntry to insert or assign (moved into the tree).

Returns

The node the entry now lives in, and whether it was made or matched.

Statuses

  • refused_k The key is absent and the allocator refused its node, so nothing was stored.
  • Any status an element's copy returns passes through unchanged.
clearRemoves all elements from the tree.
basic_wb_tree.hpp:1150 ↗
void clear() noexcept;
containsExistence check, heterogeneous when the comparator declares is_transparent.
basic_wb_tree.hpp:1166 ↗
template <typename comparable_type_ = value_t>
expected<bool> contains(comparable_type_ && comparable) const noexcept;

Parameters

comparableinObject comparable to value_t.

Returns

Always success; a lookup over owned nodes has nothing to refuse.

countNumber of elements matching comparable, which is 0 or 1 for unique keys.
basic_wb_tree.hpp:1176 ↗
template <typename comparable_type_ = value_t>
expected<std::size_t> count(
    comparable_type_ && comparable) const noexcept;

Parameters

comparableinObject comparable to value_t.

Returns

Always success; a lookup over owned nodes has nothing to refuse.

selectSelect k-th smallest element (0-indexed).
basic_wb_tree.hpp:1199 ↗
node_t * select(size_t k) noexcept;
node_t const * select(size_t k) const noexcept;

Select k-th smallest element (const version).

Runs in O(log n) expected time. Printing the median:

auto median_node = tree.select(tree.size() / 2);
if (median_node) fmt::println("Median: {}", median_node->payload);

Parameters

kinIndex of element to find (0 = minimum, size-1 = maximum).

Returns

Pointer to k-th node, or nullptr if k >= size.

rankFind rank (position) of element in sorted order.
basic_wb_tree.hpp:1218 ↗
size_t rank(value_t const & payload) const noexcept;

Runs in O(log n) expected time. Counting the elements ordered before 42:

auto position = tree.rank(42);
// position elements are smaller than 42

Parameters

payloadinEntry to find rank of.

Returns

Number of elements < payload. If element exists, this is its 0-based index. Returns size() if element is greater than all elements in tree.

augmented_sizeNumber of entries the augmentation policy counts across the whole tree.
basic_wb_tree.hpp:1224 ↗
size_t augmented_size() const noexcept;

Exact for the counts as they stand now. One scalar per node cannot encode a function of a parameter, so a reader whose predicate differs from the one currently materialized in the entries - an older snapshot, say - gets no answer from this count and none of the descents built on it.

select_augmentedSelect the k-th smallest entry among those the augmentation policy counts, O(log n).
basic_wb_tree.hpp:1230 ↗
node_t * select_augmented(size_t k) noexcept;
node_t const * select_augmented(size_t k) const noexcept;

Select over the augmented count, const version.

Returns

Pointer to the node, or nullptr when augmented_size() is at most k.

rank_augmentedNumber of counted entries ordered before comparable, in O(log n).
basic_wb_tree.hpp:1237 ↗
template <typename comparable_type_>
size_t rank_augmented(comparable_type_ && comparable) const noexcept;
refresh_augmentationTells the tree that the entry matching comparable changed its augmented count.
basic_wb_tree.hpp:1251 ↗
template <typename comparable_type_>
bool refresh_augmentation(comparable_type_ && comparable) noexcept;

Repairs the root-to-entry path in O(log n) rather than rescanning.

A wrapper calls this once per retagged entry, which may be several per write when one publish supersedes another entry.

Returns

True when the entry was found.

for_eachIterates over all entries in sorted order.
basic_wb_tree.hpp:1261 ↗
template <typename callback_type_>
status_t for_each(callback_type_ && callback) noexcept;

Parameters

callbackinCallback to invoke for each element. Must be noexcept.

Notes

A callback answering walk_control_t stops the walk where it says to.

total_imbalanceSum of absolute subtree-size differences, used by the tests to watch balance quality.
basic_wb_tree.hpp:1267 ↗
std::size_t total_imbalance() const noexcept;
erase_ifDrops every element satisfying predicate and reports how many went.
basic_wb_tree.hpp:1280 ↗
template <typename predicate_type_>
requires std::predicate<predicate_type_ &, value_t &>
std::size_t erase_if(predicate_type_ && predicate) noexcept;
erase_rangeErases the half-open range [lower, upper), invoking callback for each element.
basic_wb_tree.hpp:1297 ↗
template <typename lower_type_, typename upper_type_, typename callback_type_ = no_op_t>
void erase_range(
    lower_type_ && lower,
    upper_type_ && upper,
    callback_type_ && callback = {}) noexcept;

Splitting twice and re-joining keeps this O(log N + K) rather than K separate erases.

extractExtracts a node from the tree.
basic_wb_tree.hpp:1369 ↗
template <typename comparable_type_>
extract_result_t extract(comparable_type_ && comparable) noexcept;

The extracted node is removed from the tree and returned in an RAII wrapper.

Runs in O(log n) expected time. Moving the node for 42 into another tree:

auto extracted = tree.extract(42);
if (extracted) {
    other_tree.merge(std::move(extracted));
}

Parameters

comparableinKey to extract.

Returns

RAII wrapper containing extracted node (empty if not found).

mergeMerges an extracted node into this tree.
basic_wb_tree.hpp:1455 ↗
void merge(extract_result_t other) noexcept;
void merge(wb_tree_t & other) noexcept;

Merges another tree into this one, transferring all nodes.

void merge(wb_tree_t & other, assume_unique_t) noexcept;

Merges another tree into this one, assuming all keys are unique.

Elements with keys that already exist in this tree are deallocated (not kept in source).

Parameters

otherinExtracted node to merge.

Notes

  • Unlike std::set::merge(), a node with a duplicate key is deallocated rather than being returned. This ensures no memory leaks in a noexcept context.
  • Unlike std::set::merge(), nodes with duplicate keys are deallocated rather than remaining in the source container. This ensures no memory leaks in a noexcept context.
  • Complexity: O(m log n) where m = other.size(), n = this.size().

Preconditions

All keys in other must be different from keys in this tree.

Warnings

If precondition violated (duplicate keys exist), behavior is undefined.

rangeVisits every element in [lower, upper) in sorted order, or fewer if the callback halts.
basic_wb_tree.hpp:1534 ↗
template <typename lower_type_ = value_t, typename upper_type_ = value_t, typename callback_type_ = no_op_t>
status_t range(
    lower_type_ && lower,
    upper_type_ && upper,
    callback_type_ && callback) const noexcept;
template <typename lower_type_ = value_t, typename upper_type_ = value_t, typename callback_type_ = no_op_t>
status_t range(
    lower_type_ && lower,
    upper_type_ && upper,
    callback_type_ && callback) noexcept;

Same range walk, but the callback may modify each element in place.

find_copyCopies out the element equal to comparable.
basic_wb_tree.hpp:1556 ↗
template <typename comparable_type_>
expected<value_t> find_copy(
    comparable_type_ && comparable) const noexcept;

Statuses

  • key_not_found_k No element is equal to comparable.
  • Any status the element's copy returns passes through unchanged.
lower_bound_copyCopies out the first element not less than comparable.
basic_wb_tree.hpp:1570 ↗
template <typename comparable_type_>
expected<value_t> lower_bound_copy(
    comparable_type_ && comparable) const noexcept;

Statuses

  • key_not_found_k Every element is less than comparable.
  • Any status the element's copy returns passes through unchanged.
upper_bound_copyCopies out the first element greater than comparable.
basic_wb_tree.hpp:1584 ↗
template <typename comparable_type_>
expected<value_t> upper_bound_copy(
    comparable_type_ && comparable) const noexcept;

Statuses

  • key_not_found_k No element is greater than comparable.
  • Any status the element's copy returns passes through unchanged.
insert_if_missingInserts one element only when its key is absent, leaving any existing element alone.
basic_wb_tree.hpp:1607 ↗
template <typename comparable_type_>
inserted_iterator_t insert_if_missing(
    comparable_type_ && comparable) noexcept;
template <readable_iterator input_iterator_type_, typename... tags_types_>
requires (promises_about_the_range<tags_types_> && ...)
status_t insert_if_missing(
    input_iterator_type_ first,
    input_iterator_type_ last,
    tags_types_... tags) noexcept;

Inserts [ first, last ) where the key is free, leaving incumbents alone.

All-or-nothing over this tree from the first element on: a node the allocator refuses and an element refusing its own copy are both met while the staging tree is built, and the merge absorbing it only relinks nodes.

Statuses

  • Any status an element's copy returns passes through unchanged.
beginReturns iterator to first element (minimum).
basic_wb_tree.hpp:1858 ↗
iterator begin() noexcept;
const_iterator begin() const noexcept;

Returns const iterator to first element (minimum).

cbeginReturns const iterator to first element (minimum).
basic_wb_tree.hpp:1864 ↗
const_iterator cbegin() const noexcept;
endReturns iterator to past-the-end element.
basic_wb_tree.hpp:1867 ↗
iterator end() noexcept;
const_iterator end() const noexcept;

Returns const iterator to past-the-end element.

cendReturns const iterator to past-the-end element.
basic_wb_tree.hpp:1873 ↗
const_iterator cend() const noexcept;
findFinds an element equal to the given comparable.
basic_wb_tree.hpp:1883 ↗
template <typename comparable_type_>
iterator find(comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
const_iterator find(comparable_type_ && comparable) const noexcept;

Finds an element equal to the given comparable (const version).

Heterogeneous lookup supported if comparator defines is_transparent.

Parameters

comparableinObject comparable to value_t.

Returns

Iterator to found element, or end() if not found.

lower_boundFinds the first element not less than the given comparable (lower bound).
basic_wb_tree.hpp:1906 ↗
template <typename comparable_type_>
iterator lower_bound(comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
const_iterator lower_bound(
    comparable_type_ && comparable) const noexcept;

Finds the first element not less than the given comparable (const version).

Parameters

comparableinObject comparable to value_t.

Returns

Iterator to lower bound element, or end() if all elements < comparable.

upper_boundFinds the first element greater than the given comparable (upper bound).
basic_wb_tree.hpp:1929 ↗
template <typename comparable_type_>
iterator upper_bound(comparable_type_ && comparable) noexcept;
template <typename comparable_type_>
const_iterator upper_bound(
    comparable_type_ && comparable) const noexcept;

Finds the first element greater than the given comparable (const version).

Parameters

comparableinObject comparable to value_t.

Returns

Iterator to upper bound element, or end() if all elements <= comparable.

eraseErases a single element matching the given comparable.
basic_wb_tree.hpp:1952 ↗
template <typename comparable_type_>
bool erase(comparable_type_ && comparable) noexcept;
erase_result_t erase(iterator position) noexcept;

Erases the element at the specified iterator position.

erase_result_t erase(const_iterator position) noexcept;

Erases the element at the specified const_iterator position.

erase_result_t erase(iterator first, iterator last) noexcept;

Erases all elements in the range [first, last).

No callbacks.

Parameters

comparableinObject comparable to value_t and convertible to search key.
positioninIterator to element to erase. Must be valid and dereferenceable.
firstinIterator to first element in range.
lastinIterator to past-the-end of range.

Returns

True if element was erased, false if not found.

Notes

  • If position is end(), returns {end(), success} without modifying the tree.
  • If first == last, no elements are erased and returns {last, success}.
splitSplits this tree at a given key.
basic_wb_tree.hpp:2040 ↗
template <typename comparable_type_>
split_result_t split(comparable_type_ && comparable) noexcept;

Returns both left (< key) and right (>= key) parts. This tree is left empty.

Runs in O(log n) expected time. Splitting around 5:

auto split = tree.split(5);
// split.left has elements < 5
// split.right has elements >= 5

Parameters

comparableinKey to split at.

Returns

Split result containing left and right trees.

joinJoins another tree into this one.
basic_wb_tree.hpp:2074 ↗
void join(wb_tree_t & other) noexcept;

Precondition: all elements in this tree < all elements in other tree.

Runs in O(log n) expected time. Splitting around 5 and joining back:

auto right = tree.split(5);  // tree: [0,5), right: [5,∞)
tree.join(right);            // tree: [0,∞)

Parameters

otherinTree to join (will be empty after join).

Warnings

This is a low-level operation. The caller must ensure all(this) < all(other). For general merging with duplicate handling, use merge() instead.

key_compReturns the comparator object.
basic_wb_tree.hpp:2096 ↗
comparator_t key_comp() const noexcept;
rootReturns pointer to root node (for internal use).
basic_wb_tree.hpp:2099 ↗
node_t * root() const noexcept;
allocatorReturns reference to node allocator (for internal use).
basic_wb_tree.hpp:2102 ↗
node_allocator_t & allocator() noexcept;
node_allocator_t const & allocator() const noexcept;

Returns const reference to node allocator (for internal use).

upserted_node_tThe node an upsert settled on, and how it got there.
basic_wb_tree.hpp:1086 ↗
struct upserted_node_t

Assigning to the result overwrites that node's entry, which is what makes it usable as a handle rather than a report.

Fields

nodenode_t *
placementnode_t::node_placement_t
extract_result_tRAII wrapper for extracted nodes.
basic_wb_tree.hpp:1321 ↗
struct extract_result_t

Owns the extracted node and deallocates it when destroyed (unless released).

inserted_iterator_tWhere an insertion settled, and how it got there.
basic_wb_tree.hpp:1592 ↗
struct inserted_iterator_t

Names the same three outcomes as upserted_node_t, one level up from the nodes.

Fields

positioniteratorThe element's position, which is end() when nothing was stored.
placementnode_t::node_placement_tWhether the entry was made, matched an incumbent, or refused for want of memory.
erase_result_tResult of an erase operation on an iterator.
basic_wb_tree.hpp:1748 ↗
struct erase_result_t

Combines iterator to next element with operation status.

Fields

nextiteratorIterator to the element following the erased one, or end().
statusstatus_tStatus of the erase operation.
iteratorBidirectional iterator for WB tree.
basic_wb_tree.hpp:1758 ↗
class iterator

Provides in-order traversal of tree elements.

const_iteratorConst bidirectional iterator for WB tree.
basic_wb_tree.hpp:1809 ↗
class const_iterator

Provides in-order traversal of tree elements (read-only).

split_result_tResult of splitting a tree at a key.
basic_wb_tree.hpp:2012 ↗
struct split_result_t

Contains two trees: left (all < key) and right (all >= key).

Fields

leftwb_tree_tTree with all elements ordered before the split key.
rightwb_tree_tTree with all elements not ordered before the split key.
Classhash_atomic_slot_refThread-safe smart reference to a hash table slot with metadata accessors.10
hash_layout.hpp:426 ↗
template <typename value_type_,
          typename hasher_type_,
          waiting_policy<std::uint64_t const *, std::uint64_t> waiting_policy_type_ = bare_waiting_policy_t,
          template<typename> class atomic_reference_ = atomic_ref>
class hash_atomic_slot_ref

Ideally, we would want to avoid Compare-And-Swap (CAS) loops for locking individual slots. On the locking path, we can use a fetch_or atomic operation to set both bits (populated + deleted) simultaneously and transition to the locked state. If the previous value indicates the slot was already locked, we retry until we acquire the lock.

The intuition of using the fetch_and for the inverse operation, however, is wrong. When unlocking, we need to restore the previous state (populated/deleted) of the slot or transition to the new one, depending on the mark_populated()/mark_deleted()/mark_free() calls made while the slot was locked.

The bits outside the active ones in each 32-bit word shouldn't be changed. The active ones may have to be flipped. Assuming the value of the relevant bits couldn't have changed, we can use fetch_xor to control the result with one more atomic and no compare-and-swap loop. XOR-is-all-you-need!

Neither path is lock-free in the technical sense: lock() spins until it wins the slot, so a thread stopped between lock() and unlock() blocks every prober that reaches that slot. What the pair buys is that the whole table is never locked, and that no slot state needs a CAS retry. What a losing prober does with its core in the meantime is waiting_policy_type_, which is where a caller that owns the hardware spends a stall instead of an issue slot.

This doesn't resolve false-sharing issues native to such a densely packed design, but still results in very low contention if the duration of atomic operations under the lock is comparable to CPU's memory latency.

header_refThe staged header, not the shared one, so reads under the lock stay private.
hash_layout.hpp:449 ↗
hash_bucket_head_t & header_ref() const noexcept;
is_freeWhether the staged state is free_k.
hash_layout.hpp:452 ↗
bool is_free() const noexcept;
is_deletedWhether the staged state is deleted_k.
hash_layout.hpp:457 ↗
bool is_deleted() const noexcept;
is_populatedWhether the staged state is populated_k.
hash_layout.hpp:462 ↗
bool is_populated() const noexcept;
is_lockedWhether the staged state is locked_k.
hash_layout.hpp:467 ↗
bool is_locked() const noexcept;
mark_freeStages free_k, which unlock() then publishes.
hash_layout.hpp:472 ↗
void mark_free() const noexcept;
mark_populatedStages populated_k, which unlock() then publishes.
hash_layout.hpp:475 ↗
void mark_populated() const noexcept;
mark_deletedStages deleted_k, which unlock() then publishes.
hash_layout.hpp:478 ↗
void mark_deleted() const noexcept;
lockSpins on fetch_or until this thread is the one that observed a non-locked slot.
hash_layout.hpp:488 ↗
void lock() const noexcept;

Warnings

On a device this spin only makes progress under independent thread scheduling, since 32 slots share one header and a warp probing one bucket serializes through here.

Notes

The reference is a temporary rather than a named variable because a constexpr function may not define a variable of non-literal type before C++23. It costs nothing - the reference only carries the address it was handed.

unlockDrives the two owned bits from locked_k to whatever was staged, with one posted xor.
hash_layout.hpp:509 ↗
void unlock() const noexcept;
Classimmutable_b_treeAn immutable B-tree over value_type_, built once from a sorted span, whose nodes are rows of keys_per_row_ keys searched by row_kit_type_.16
immutable_b_tree.hpp:49 ↗
template <typename value_type_,
          std::size_t keys_per_row_ = keys_per_row<typename mapping_key_type_or_itself<value_type_>::type>(default_row_bytes_k),
          row_kit row_kit_type_ = native_row_kit_t,
          typename allocator_type_ = default_allocator<value_type_>>
class immutable_b_tree

A set holds one allocation of rows; a map holds a second one of mapped values, indexed by rank.

value_tThe whole stored element: the key for a set, a mapping of both halves for a map.
immutable_b_tree.hpp:52 ↗
using value_t = value_type_;
key_tThe key half the rows are searched on, which is the whole element for a set.
immutable_b_tree.hpp:56 ↗
using key_t = typename mapping_key_type_or_itself<value_t>::type;
mapped_tThe mapped half, held in rank order beside the rows, and void for a set.
immutable_b_tree.hpp:60 ↗
using mapped_t = typename mapped_value_type_or_void<value_t>::type;
mapped_slot_tWhat one entry of the mapped array holds; an empty stand-in when the tree is a set.
immutable_b_tree.hpp:64 ↗
using mapped_slot_t = std::conditional_t<is_mapping<value_t>, mapped_t, placeholder_t>;
immutable_b_tree
immutable_b_tree.hpp:145 ↗
immutable_b_tree() noexcept=default;
immutable_b_tree(allocator_t allocator) noexcept;
immutable_b_tree(immutable_b_tree && other) noexcept;
immutable_b_tree(immutable_b_tree const &)=delete;
operator=
immutable_b_tree.hpp:154 ↗
immutable_b_tree & operator=(immutable_b_tree && other) noexcept;
immutable_b_tree & operator=(immutable_b_tree const &)=delete;
size_bytesThe bytes this tree stores, rows and mapped values together.
immutable_b_tree.hpp:213 ↗
std::size_t size_bytes() const noexcept;
std::size_t size_bytes(std::size_t keys_count) noexcept;

The bytes a tree over keys_count elements stores: whole rows of keys, and a value per key for a map.

wordsThe stored words, node after node, for writing the tree out.
immutable_b_tree.hpp:226 ↗
std::span<word_t const> words() const noexcept;
*× 3How many keys order below wanted, which is also the position of its lower bound.
immutable_b_tree.hpp:229 ↗
std::size_t rank(key_t wanted) const noexcept;
*× 3The position of wanted, or key_not_found_k.
immutable_b_tree.hpp:232 ↗
expected<std::size_t> find(key_t wanted) const noexcept;

Where keys repeat, the first of them.

*× 3The key at position ordinal in sorted order, which must be below size.
immutable_b_tree.hpp:241 ↗
key_t select(std::size_t ordinal) const noexcept;
mapped_*_at× 3The value the key at position ordinal carries, which must be below size.
immutable_b_tree.hpp:248 ↗
requires is_mapping<value_t>
mapped_slot_t const & mapped__at(std::size_t ordinal) const noexcept;
at_*_rank× 3A walk from position ordinal, or end when there is no such position.
immutable_b_tree.hpp:259 ↗
iterator at__rank(std::size_t ordinal) const noexcept;
lower_*_bound× 3A walk from the first key not below wanted.
immutable_b_tree.hpp:264 ↗
iterator lower__bound(key_t wanted) const noexcept;
*× 2Builds the tree over sorted, which may repeat keys.
immutable_b_tree.hpp:176 ↗
expected<immutable_b_tree> make(
    std::span<value_t const> sorted,
    allocator_t allocator = {}) noexcept;

A map keeps the mapped values in the order they arrive, so a repeated key reads back the value that came first.

Returns

The tree.

Statuses

iteratorA forward walk over the keys in order, yielding each by value.
immutable_b_tree.hpp:104 ↗
class iterator

A map walks its keys too, and rank indexes mapped_at for the value beside one.

Classimmutable_elias_fanoAn immutable sequence of non-decreasing unsigned integers, built once from values pulled in order and read back by position - the offsets of variable-length rows, a few bits apiece.6
immutable_elias_fano.hpp:45 ↗
template <typename value_type_ = std::uint64_t,
          allocator_of<std::uint64_t> allocator_type_ = default_allocator<std::uint64_t>,
          row_kit row_kit_type_ = native_row_kit_t>
class immutable_elias_fano

row_kit_type_ scans the high parts in select where it has a scan of its own.

select_stride_kSet bits between consecutive samples of the select index.
immutable_elias_fano.hpp:53 ↗
std::size_t select_stride_k;
immutable_elias_fano
immutable_elias_fano.hpp:71 ↗
immutable_elias_fano() noexcept=default;
immutable_elias_fano(allocator_t allocator) noexcept;
immutable_elias_fano(immutable_elias_fano && other) noexcept;
immutable_elias_fano(immutable_elias_fano const &)=delete;
operator=
immutable_elias_fano.hpp:81 ↗
immutable_elias_fano & operator=(
    immutable_elias_fano && other) noexcept;
immutable_elias_fano & operator=(immutable_elias_fano const &)=delete;
size_bytesThe bytes this sequence stores: the low parts, the high parts and the select samples.
immutable_elias_fano.hpp:158 ↗
std::size_t size_bytes() const noexcept;
*× 3The value at position.
immutable_elias_fano.hpp:163 ↗
value_t select(std::size_t position) const noexcept;
Symbol
select
operator[]
operator[]
makeBuilds the sequence of count values, none above universe, pulled in order through fill, so no caller ever holds them all at once.
immutable_elias_fano.hpp:109 ↗
template <typename fill_type_>
expected<immutable_elias_fano> make(
    std::size_t count,
    value_t universe,
    fill_type_ && fill,
    allocator_t allocator = {}) noexcept;
expected<immutable_elias_fano> make(
    std::span<value_t const> sorted,
    allocator_t allocator = {}) noexcept;

Builds the sequence of sorted, held whole.

Parameters

countinNumber of values in the sequence.
universeinLargest value any of them may take.
fillinAnswers expected<std::span<value_t const>> for a position, holding the values from that position on - at least one while any remain, possibly all of them.
allocatorinThe allocator instance to use.

Returns

The sequence.

Statuses

Classimmutable_packed_arrayAn immutable array of unsigned integers between a base and a maximum, built once from values pulled in order, each kept in the bit width of the maximum less the base.8
immutable_packed_array.hpp:152 ↗
template <std::unsigned_integral value_type_ = std::uint64_t,
          allocator_of<std::uint64_t> allocator_type_ = default_allocator<std::uint64_t>>
class immutable_packed_array
immutable_packed_array
immutable_packed_array.hpp:176 ↗
immutable_packed_array() noexcept=default;
immutable_packed_array(allocator_t allocator) noexcept;
immutable_packed_array(immutable_packed_array && other) noexcept;
immutable_packed_array(immutable_packed_array const &)=delete;
operator=
immutable_packed_array.hpp:184 ↗
immutable_packed_array & operator=(
    immutable_packed_array && other) noexcept;
immutable_packed_array & operator=(
    immutable_packed_array const &)=delete;
bitsHow many bits each value takes: the bit width of the maximum less the base.
immutable_packed_array.hpp:265 ↗
unsigned bits() const noexcept;
*× 2The distance every stored value is measured from, and the least value it may hold.
immutable_packed_array.hpp:268 ↗
value_t base() const noexcept;
size_bytesThe bytes the packed words take, which a zero width keeps at zero.
immutable_packed_array.hpp:271 ↗
std::size_t size_bytes() const noexcept;
saved_bytesThe bytes save writes: the header and then the packed words.
immutable_packed_array.hpp:274 ↗
std::size_t saved_bytes() const noexcept;
saveWrites the saved form into the front of bytes, for immutable_packed_array_view to read in place.
immutable_packed_array.hpp:284 ↗
status_t save(std::span<std::byte> bytes) const noexcept;

Statuses

makeBuilds the array of count values from base to maximum inclusive, pulled in order through fill, so no caller ever holds them all at once.
immutable_packed_array.hpp:214 ↗
template <typename fill_type_>
expected<immutable_packed_array> make(
    std::size_t count,
    value_t base,
    value_t maximum,
    fill_type_ && fill,
    allocator_t allocator = {}) noexcept;
expected<immutable_packed_array> make(
    std::size_t count,
    value_t value,
    allocator_t allocator = {}) noexcept;

Builds the array of count copies of value, which takes no bits and no words.

expected<immutable_packed_array> make(
    std::span<value_t const> values,
    allocator_t allocator = {}) noexcept;

Builds the array of values, held whole, based at the least of them and as wide as the greatest needs.

Parameters

countinNumber of values in the array.
baseinSmallest value any of them may take.
maximuminLargest value any of them may take.
fillinAnswers expected<std::span<value_t const>> for a position, holding the values from that position on - at least one while any remain, possibly all of them.
allocatorinThe allocator instance to use.

Returns

The array.

Statuses

Classimmutable_packed_array_viewAn immutable packed array read in place from bytes it does not own, as saved.4
immutable_packed_array.hpp:79 ↗
template <std::unsigned_integral value_type_ = std::uint64_t>
class immutable_packed_array_view

A pointer and three fields, so it copies freely and stays valid exactly as long as the bytes it was made over.

immutable_packed_array_viewDefault-constructs an empty immutable_packed_array_view.
immutable_packed_array.hpp:98 ↗
constexpr immutable_packed_array_view() noexcept=default;
bitsHow many bits each value takes.
immutable_packed_array.hpp:130 ↗
unsigned bits() const noexcept;
saved_*_bytes× 2The bytes the saved form spans, so a reader can step past it to whatever follows.
immutable_packed_array.hpp:136 ↗
std::size_t saved__bytes() const noexcept;
makeReads the saved form at the front of bytes, which may sit at any alignment.
immutable_packed_array.hpp:108 ↗
expected<immutable_packed_array_view> make(
    std::span<std::byte const> bytes) noexcept;

Returns

The view.

Statuses

Classimmutable_splus_treeAn immutable S+ tree over value_type_, built once from a sorted span, whose nodes are rows of keys_per_row_ keys searched by row_kit_type_.13
immutable_splus_tree.hpp:321 ↗
template <typename value_type_,
          std::size_t keys_per_row_ = keys_per_row<typename mapping_key_type_or_itself<value_type_>::type>(default_row_bytes_k),
          row_kit row_kit_type_ = native_row_kit_t,
          typename allocator_type_ = default_allocator<value_type_>>
class immutable_splus_tree

A set holds one allocation of rows; a map holds a second one of mapped values, indexed by rank.

view_tThe in-place view every lookup goes through, over this tree's storage or saved bytes.
immutable_splus_tree.hpp:324 ↗
using view_t = immutable_splus_tree_view<value_type_, keys_per_row_, row_kit_type_>;
value_tEvery stored element: the key itself for a set, a mapping of both halves for a map.
immutable_splus_tree.hpp:327 ↗
using value_t = value_type_;
key_tThe key half the rows are searched on, which is the whole element for a set.
immutable_splus_tree.hpp:331 ↗
using key_t = typename view_t::key_t;
mapped_tThe mapped half, held in rank order beside the rows, and void for a set.
immutable_splus_tree.hpp:335 ↗
using mapped_t = typename view_t::mapped_t;
mapped_slot_tWhat one entry of the mapped array holds: an empty stand-in when the tree is a set.
immutable_splus_tree.hpp:339 ↗
using mapped_slot_t = typename view_t::mapped_slot_t;
iteratorA forward walk over the keys in order, as the view walks them.
immutable_splus_tree.hpp:350 ↗
using iterator = typename view_t::iterator;
immutable_splus_tree
immutable_splus_tree.hpp:367 ↗
immutable_splus_tree() noexcept=default;
immutable_splus_tree(allocator_t allocator) noexcept;
immutable_splus_tree(immutable_splus_tree && other) noexcept;
immutable_splus_tree(immutable_splus_tree const &)=delete;
operator=
immutable_splus_tree.hpp:376 ↗
immutable_splus_tree & operator=(
    immutable_splus_tree && other) noexcept;
immutable_splus_tree & operator=(immutable_splus_tree const &)=delete;
size_bytesThe bytes this tree stores, rows and mapped values together.
immutable_splus_tree.hpp:447 ↗
std::size_t size_bytes() const noexcept;
std::size_t size_bytes(std::size_t keys_count) noexcept;

The bytes a tree over keys_count elements stores: whole rows on every level, and a value per key for a map.

wordsThe stored words, root level first, for writing the tree out.
immutable_splus_tree.hpp:458 ↗
std::span<word_t const> words() const noexcept;
viewThis tree searched in place, valid while the tree lives and stays where it is.
immutable_splus_tree.hpp:461 ↗
view_t const & view() const noexcept;
saved_bytesThe bytes save writes: the header, the rows, and the mapped values on their alignment.
immutable_splus_tree.hpp:464 ↗
std::size_t saved_bytes() const noexcept;
saveWrites the saved form into the front of bytes, for immutable_splus_tree_view to search in place.
immutable_splus_tree.hpp:473 ↗
requires std::is_trivially_copyable_v<mapped_slot_t>
status_t save(std::span<std::byte> bytes) const noexcept;

Statuses

Classimmutable_splus_tree_viewAn immutable S+ tree searched in place, over rows and mapped values it does not own: either a saved form in borrowed bytes, or the storage of the tree it was taken from.11
immutable_splus_tree.hpp:77 ↗
template <typename value_type_,
          std::size_t keys_per_row_ = keys_per_row<typename mapping_key_type_or_itself<value_type_>::type>(default_row_bytes_k),
          row_kit row_kit_type_ = native_row_kit_t>
class immutable_splus_tree_view

A few pointers and the level offsets, so it copies freely and stays valid exactly as long as the bytes it was made over.

value_tEvery stored element: the key itself for a set, a mapping of both halves for a map.
immutable_splus_tree.hpp:80 ↗
using value_t = value_type_;
key_tThe key half the rows are searched on, which is the whole element for a set.
immutable_splus_tree.hpp:84 ↗
using key_t = typename mapping_key_type_or_itself<value_t>::type;
mapped_tThe mapped half, held in rank order beside the rows, and void for a set.
immutable_splus_tree.hpp:88 ↗
using mapped_t = typename mapped_value_type_or_void<value_t>::type;
mapped_slot_tWhat one entry of the mapped array holds: an empty stand-in when the tree is a set.
immutable_splus_tree.hpp:92 ↗
using mapped_slot_t = std::conditional_t<is_mapping<value_t>, mapped_t, placeholder_t>;
levels_capacity_kMore levels than a fanout of three needs for 2^64 keys.
immutable_splus_tree.hpp:104 ↗
std::size_t levels_capacity_k;
saved_rows_offset_kWhere the rows start in the saved form, right after the header.
immutable_splus_tree.hpp:107 ↗
std::size_t saved_rows_offset_k;
saved_alignment_kThe boundary saved bytes start on, so the kits read whole words and mapped values align.
immutable_splus_tree.hpp:110 ↗
std::size_t saved_alignment_k;
immutable_splus_tree_viewDefault-constructs an empty immutable_splus_tree_view.
immutable_splus_tree.hpp:169 ↗
constexpr immutable_splus_tree_view() noexcept=default;
size_bytesThe bytes the rows and the mapped values take.
immutable_splus_tree.hpp:203 ↗
std::size_t size_bytes() const noexcept;
std::size_t size_bytes(std::size_t keys_count) noexcept;

The bytes a tree over keys_count elements stores: whole rows on every level, and a value per key for a map.

makeReads the saved form at the front of bytes, starting on saved_alignment_k.
immutable_splus_tree.hpp:179 ↗
expected<immutable_splus_tree_view> make(
    std::span<std::byte const> bytes) noexcept;

Returns

The view.

Statuses

  • invalid_argument_k bytes is misaligned, ends before the rows and mapped values its header names, or was saved by a tree of another key, row width or mapped value.
iteratorA forward walk over the keys in order, yielding each by value.
immutable_splus_tree.hpp:132 ↗
class iterator

A map walks its keys too, and rank indexes mapped_at for the value beside one.

Structhash_storageThe one allocation a table is carved from, and the counters describing it.25
hash_layout.hpp:652 ↗
template <typename value_type_,
          typename hasher_type_,
          allocator_of<std::byte> allocator_type_>
struct hash_storage

Owns the buffer and the elements inside it, and frees both, so a table holding one needs no destructor of its own. This is also the only thing that passes between a growable table and a pinned one, which is what lets that hand-off be a move of a value rather than one type reaching into the other.

has_values_kWhether the element carries a mapped value, making the table a map rather than a set.
hash_layout.hpp:662 ↗
bool has_values_k;
bytes_in_bucket_kBytes one bucket occupies across all three regions, header included.
hash_layout.hpp:665 ↗
std::size_t bytes_in_bucket_k;
destruct_keys_kWhether teardown must run a key destructor.
hash_layout.hpp:668 ↗
bool destruct_keys_k;
destruct_values_kWhether teardown must run a value destructor.
hash_layout.hpp:671 ↗
bool destruct_values_k;
memoryBase of the single allocation the three regions are carved from.
hash_layout.hpp:675 ↗
std::byte * memory;
keysKeys region, indexed by slot.
hash_layout.hpp:678 ↗
key_t * keys;
valuesValues region, indexed by slot, null for sets.
hash_layout.hpp:681 ↗
mapped_storage_t * values;
headersHeaders region, indexed by bucket.
hash_layout.hpp:684 ↗
hash_bucket_head_t * headers;
slots_countTotal number of slots, always a power of two.
hash_layout.hpp:687 ↗
offset_t slots_count;
growth_thresholdRehash trigger at the 75% load factor.
hash_layout.hpp:690 ↗
offset_t growth_threshold;
populated_countCount of populated slots.
hash_layout.hpp:693 ↗
offset_t populated_count;
deleted_countCount of deleted slots still holding tombstones.
hash_layout.hpp:696 ↗
offset_t deleted_count;
allocatorSupplies and reclaims the single byte buffer behind the three regions.
hash_layout.hpp:699 ↗
allocator_t allocator;
hash_storage
hash_layout.hpp:701 ↗
hash_storage() noexcept=default;
hash_storage(allocator_t allocator_state) noexcept;
hash_storage(hash_storage && other) noexcept;
hash_storage(hash_storage const &)=delete;
operator=
hash_layout.hpp:704 ↗
hash_storage & operator=(hash_storage && other) noexcept;
hash_storage & operator=(hash_storage const &)=delete;
~hash_storage
hash_layout.hpp:710 ↗
~hash_storage() noexcept;
is_allocatedWhether a buffer is behind the three regions.
hash_layout.hpp:752 ↗
bool is_allocated() const noexcept;
retarget_regionsRecomputes the three region pointers from memory and slots_count.
hash_layout.hpp:758 ↗
void retarget_regions() noexcept;
retarget_slotPoints slot at the three regions and at slot_index within them.
hash_layout.hpp:774 ↗
template <typename slot_ref_type_>
void retarget_slot(
    slot_ref_type_ & slot,
    offset_t slot_index) const noexcept;
destroy_elementsRuns the destructors of every live key and value, leaving the headers as they are.
hash_layout.hpp:782 ↗
void destroy_elements() noexcept;
clearDestroys every element and zeroes the headers, keeping the buffer: a slot's state lives in its header alone, so the keys and values left behind are never taken for elements.
hash_layout.hpp:800 ↗
void clear() noexcept;
deallocateReturns the buffer to the allocator and drops every pointer and counter.
hash_layout.hpp:812 ↗
void deallocate() noexcept;

Warnings

Elements are not destroyed, so call clear or destroy_elements first.

swapA cheap copy-less exchange of the buffer, the counters and the allocator.
hash_layout.hpp:825 ↗
void swap(hash_storage & other) noexcept;
makeAllocates and zeroes the buffer for slots, keeping allocator for its release.
hash_layout.hpp:720 ↗
hash_storage make(
    hash_slots_count_t slots,
    allocator_t allocator_state) noexcept;

Returns

An empty storage when the request is unrepresentable or the allocation fails, which is_allocated reports.

memory_usageTotal memory needed for the three regions of a table this size.
hash_layout.hpp:747 ↗
std::size_t memory_usage(hash_slots_count_t slots) noexcept;
Structintrusive_listThe oldest and newest of a first-in first-out list of record indices.6
intrusive_list.hpp:41 ↗
template <std::unsigned_integral index_type_ = std::uint32_t>
struct intrusive_list
absent_kEnds a list and names no record.
intrusive_list.hpp:45 ↗
index_t absent_k;
oldestThe record pop answers next.
intrusive_list.hpp:48 ↗
index_t oldest;
newestThe record push linked last.
intrusive_list.hpp:51 ↗
index_t newest;
pushLinks the record at index after the newest.
intrusive_list.hpp:58 ↗
template <typename records_type_>
requires singly_linked_records<records_type_, index_t>
void push(records_type_ & records, index_t index) noexcept;
popUnlinks the oldest record and answers its index, or absent_k when the list is empty.
intrusive_list.hpp:69 ↗
template <typename records_type_>
requires singly_linked_records<records_type_, index_t>
index_t pop(records_type_ & records) noexcept;
eraseUnlinks the record at index from wherever it sits.
intrusive_list.hpp:88 ↗
template <typename records_type_>
requires singly_linked_records<records_type_, index_t>
status_t erase(records_type_ & records, index_t index) noexcept;

Statuses

  • key_not_found_k The record was never pushed, or was popped or erased since. A record of another list is not detected.
Structno_entropy_tA generator that yields nothing, so a concept can probe a sampling call without drawing.4
shared.hpp:755 ↗
struct no_entropy_t

The counterpart of no_op_t on the other slot: a concept needs a value of some type to write the call it is asking about, and binding a callback there would leave a swapped generator and callback invisible. Nothing samples through this - draw_below wants min, max and a call operator, and this is the least that satisfies all three.

result_typeWhat a draw hands back, widest so a probe never narrows what a real generator would give.
shared.hpp:758 ↗
using result_type = std::uint64_t;
minThe lowest value a draw can take, which draw_below subtracts to normalise.
shared.hpp:761 ↗
result_type min() noexcept;
maxThe highest value a draw can take, which draw_below spans to size its mask.
shared.hpp:764 ↗
result_type max() noexcept;
operator()Draws nothing, since no concept using this ever runs the body it probes.
shared.hpp:767 ↗
result_type operator()() noexcept;
Structserial_row_kit_tThe reference kit every kit must agree with, the one each finishes a partial chunk with.6
row_search.hpp:175 ↗
struct serial_row_kit_t
mask_equal_width_kKeys one mask_equal compares, its answer in the low bits of a 32-bit mask.
row_search.hpp:180 ↗
std::size_t mask_equal_width_k;
count_belowHow many of keys order below wanted; for a sorted row, the lower bound.
row_search.hpp:185 ↗
template <typename key_type_, std::size_t extent_>
requires row_searchable_key<key_type_>
std::size_t count_below(
    std::span<key_type_ const, extent_> keys,
    std::type_identity_t<key_type_> wanted) noexcept;
template <std::size_t extent_>
std::size_t count_below(
    std::span<std::uint64_t const, extent_> high_words,
    std::span<std::uint64_t const, extent_> low_words,
    key128_t wanted) noexcept;

How many keys of a split row order below wanted, reading a low word only where its high word ties.

mask_equalWhich of keys equal wanted, one bit per key, the lowest for the first: a hash table's window, where slots nobody filled compare too and the caller masks them off.
row_search.hpp:211 ↗
template <typename word_type_>
requires std::same_as<word_type_, std::uint32_t> || std::same_as<word_type_, std::uint64_t> || std::same_as<word_type_, key128_t>
std::uint32_t mask_equal(
    std::span<word_type_ const, mask_equal_width_k> keys,
    word_type_ wanted) noexcept;
select_set_bitWhere the remaining -th set bit after set bit sampled of words sits, counting sampled as the zeroth: the scan an Elias-Fano select runs from its sample.
row_search.hpp:227 ↗
std::uint64_t select_set_bit(
    std::span<std::uint64_t const> words,
    std::uint64_t sampled,
    std::size_t remaining) noexcept;

remaining must be positive. A bit words does not hold, which only a corrupted sequence asks for, answers words.size() * 64, past every real bit, rather than reading past the span. Kits scanning several words per instruction finish the span's last few here.

select_onwardContinues a select at word, what remains to count of the word at word_index, never reading past words.
row_search.hpp:236 ↗
std::uint64_t select_onward(
    std::span<std::uint64_t const> words,
    std::size_t word_index,
    std::uint64_t word,
    std::size_t remaining) noexcept;
select_in_wordThe offset of the rank -th set bit of word, counted from zero, found by halving.
row_search.hpp:248 ↗
unsigned select_in_word(std::uint64_t word, std::size_t rank) noexcept;

Functions

first_ofThe first of a pack, for a caller that needs one representative of several arguments.
shared.hpp:601 ↗
template <typename first_type_, typename... rest_types_>
first_type_ & first_of(first_type_ & first, rest_types_ & ...) noexcept;
advance_iteratorSteps iterator forward count positions, in one jump where it supports +=.
shared.hpp:644 ↗
template <readable_iterator iterator_type_>
void advance_iterator(
    iterator_type_ & iterator,
    std::size_t count) noexcept;
distance_betweenHow many steps separate two positions, in one subtraction where they support it.
shared.hpp:717 ↗
template <multipass_iterator iterator_type_, iterator_sentinel<iterator_type_> sentinel_type_>
std::size_t distance_between(
    iterator_type_ first,
    sentinel_type_ const & last) noexcept;
upper_bound_or_endThe first position of collection past upper, or its end where upper is unbounded.
shared.hpp:846 ↗
template <typename collection_type_, typename upper_type_>
auto upper_bound_or_end(
    collection_type_ const & collection,
    upper_type_ const & upper) noexcept;
above_untilWhether key orders above until under less, which every key does where until is unbounded, so a descending walk stops on the first key that does not.
shared.hpp:854 ↗
template <typename less_type_, typename key_type_, typename until_type_>
bool above_until(
    less_type_ const & less,
    key_type_ const & key,
    until_type_ const & until) noexcept;
contains_typeCompile-time check if a type appears in parameter pack.
shared.hpp:867 ↗
template <typename needle_type_, typename... haystack_types_>
bool contains_type();

Returns

True if needle_type_ found in haystack_types_.

atomic_add_fetchRelaxed atomic increment of a plain counter, returning the post-increment value.
shared.hpp:1967 ↗
template <std::integral integral_type_>
integral_type_ atomic_add_fetch(
    integral_type_ & counter,
    integral_type_ addend) noexcept;

Relaxed suffices for a counter read by value - a statistic, or a version stamp compared for identity and recency - because one location has a total modification order. It publishes nothing: a caller needing the data a counter describes to be visible must order that itself.

atomic_loadAtomic read of a counter other threads may be incrementing, relaxed unless order asks for more.
shared.hpp:1979 ↗
template <std::integral integral_type_>
integral_type_ atomic_load(
    integral_type_ const & counter,
    memory_order_t order = memory_order_relaxed_k) noexcept;

Needed wherever a plain read would race an atomic_add_fetch on the same field.

Warnings

The counter is read through a const path but must not be a const object - an atomic_ref over one is undefined. Every caller reaches a mutable member of a mutable container through a const member function, which is what makes the cast below sound.

atomic_storeRelaxed atomic write of a counter other threads may be reading.
shared.hpp:1988 ↗
template <std::integral integral_type_>
void atomic_store(
    integral_type_ & counter,
    integral_type_ value) noexcept;

The mirror of atomic_load, and just as unordered: a caller needing the data a counter describes to be visible alongside it must order that itself.

atomic_load_newestReads word through a read-modify-write, getting the newest value, never an older one.
shared.hpp:2000 ↗
template <std::integral integral_type_>
integral_type_ atomic_load_newest(integral_type_ & word) noexcept;

A plain load may read a stale write; a read-modify-write reads the newest and joins its release sequence, so a store made before this call is visible to whoever reads the word afterwards. That is what an announce-then-re-read protocol needs and what a load cannot give it.

atomic_sub_fetchRelaxed atomic decrement of a plain counter, returning the post-decrement value.
shared.hpp:2006 ↗
template <std::integral integral_type_>
integral_type_ atomic_sub_fetch(
    integral_type_ & counter,
    integral_type_ subtrahend) noexcept;
atomic_waitParks until counter stops reading observed, so a waiter costs no core as it waits.
shared.hpp:2016 ↗
template <std::integral integral_type_>
void atomic_wait(
    integral_type_ const & counter,
    integral_type_ observed) noexcept;

Warnings

Wakes only where writers call atomic_notify_all; bare atomic_store wakes nobody.

See also

The same const-path caveat as atomic_load applies to the cast.

atomic_notify_allWakes every waiter parked on counter.
shared.hpp:2022 ↗
template <std::integral integral_type_>
void atomic_notify_all(integral_type_ & counter) noexcept;

The mirror of atomic_wait.

atomic_post_addPosts addend onto word without reading what was there.
shared.hpp:2066 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
void atomic_post_add(
    integral_type_ & word,
    integral_type_ addend,
    order_type_ order) noexcept;
atomic_post_subPosts subtrahend off word without reading what was there.
shared.hpp:2074 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
void atomic_post_sub(
    integral_type_ & word,
    integral_type_ subtrahend,
    order_type_ order) noexcept;
atomic_post_set_bitsPosts bits set in word without reading what was there.
shared.hpp:2083 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
void atomic_post_set_bits(
    integral_type_ & word,
    integral_type_ bits,
    order_type_ order) noexcept;
atomic_post_clear_bitsPosts bits cleared in word without reading what was there.
shared.hpp:2092 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
void atomic_post_clear_bits(
    integral_type_ & word,
    integral_type_ bits,
    order_type_ order) noexcept;
atomic_max_fetchRaises word to floor unless it stands higher, so a stale floor never lowers it.
shared.hpp:2106 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_>
void atomic_max_fetch(
    integral_type_ & word,
    integral_type_ floor) noexcept;

One instruction through a reference that spells fetch_max, and the compare-exchange loop it replaces otherwise. A word only ever raised this way is monotone whoever writes it.

atomic_fetch_maxRaises word to floor unless it stands higher, answering what it held before.
shared.hpp:2127 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
integral_type_ atomic_fetch_max(
    integral_type_ & word,
    integral_type_ floor,
    order_type_ order) noexcept;

One instruction through a reference that spells fetch_max, which std::atomic_ref does not before C++26, and the compare-exchange loop it replaces otherwise. A word only ever raised this way is monotone whoever writes it.

Returns

What word held before, at or above floor exactly when nothing was written.

atomic_post_maxRaises word to floor without reading what was there, which Arm spells stumax.
shared.hpp:2141 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
void atomic_post_max(
    integral_type_ & word,
    integral_type_ floor,
    order_type_ order) noexcept;
atomic_post_flip_bitsPosts bits flipped in word without reading what was there.
shared.hpp:2147 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
void atomic_post_flip_bits(
    integral_type_ & word,
    integral_type_ bits,
    order_type_ order) noexcept;
atomic_fetch_add_if_at_mostAdds addend to word only while the sum stays at or below limit.
shared.hpp:2163 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
integral_type_ atomic_fetch_add_if_at_most(
    integral_type_ & word,
    integral_type_ addend,
    integral_type_ limit,
    order_type_ order) noexcept;

The bound is the whole point: a predicate a loop would re-check after every failed exchange becomes an argument the instruction decides on, so a refused caller writes nothing and leaves the line where it was. Without the extended form this is the compare-exchange loop it replaces.

Returns

What word held before, which is above limit exactly when nothing was written.

atomic_fetch_sub_if_at_leastSubtracts subtrahend from word only while the difference stays at or above floor.
shared.hpp:2183 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
integral_type_ atomic_fetch_sub_if_at_least(
    integral_type_ & word,
    integral_type_ subtrahend,
    integral_type_ floor,
    order_type_ order) noexcept;

Returns

What word held before, which is below floor exactly when nothing was written.

atomic_fetch_test_and_set_bitSets bit bit of word, below its width, and answers whether it was set before: a claim exactly one of several racing callers wins.
shared.hpp:2200 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
bool atomic_fetch_test_and_set_bit(
    integral_type_ & word,
    unsigned bit,
    order_type_ order) noexcept;
atomic_fetch_test_and_reset_bitClears bit bit of word, below its width, and answers whether it was set before.
shared.hpp:2210 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::integral integral_type_, typename order_type_>
bool atomic_fetch_test_and_reset_bit(
    integral_type_ & word,
    unsigned bit,
    order_type_ order) noexcept;
atomic_claim_first_clear_bitClaims a clear bit of words with an acquiring set, scanning words in order and each word from its lowest clear bit.
shared.hpp:2228 ↗
template <template<typename> class atomic_reference_ = atomic_ref, std::unsigned_integral word_type_>
std::size_t atomic_claim_first_clear_bit(
    std::span<word_type_> words) noexcept;

A lost race marks only the bit it lost and tries the next clear one of the same word, so a word costs one load and one claim per bit taken from under the caller. Bits released behind the scan are not seen. Release a claim with atomic_post_clear_bits at release order.

Returns

The claimed bit's position across words, or the total bit count when none was clear.

larger_ofThe larger of two values, and the smaller.
shared.hpp:2255 ↗
template <typename value_type_>
value_type_ const & larger_of(
    value_type_ const & first,
    value_type_ const & second) noexcept;

Spelled here rather than pulled from <algorithm>, which costs about ten thousand preprocessed lines for these two. It also sidesteps the Windows headers, which define min and max as function-like macros unless NOMINMAX is set, and would eat an unparenthesized std::max call.

bits_to_holdThe number of bits needed to represent x, so 0 for zero and 1 for one.
shared.hpp:2271 ↗
std::size_t bits_to_hold(std::size_t x) noexcept;
roundup_to_pow2Rounds up an integer to the next power of two.
shared.hpp:2280 ↗
std::size_t roundup_to_pow2(std::size_t x) noexcept;

Returns 0 for input 0, and 1 for input 1.

Parameters

xinValue to round up.

Returns

Smallest power of two greater than or equal to x, or 0 when there is no such power.

draw_belowA uniform draw below bound, without the bias a modulo would introduce.
shared.hpp:2320 ↗
template <typename generator_type_>
requires uniform_random_bits<std::remove_cvref_t<generator_type_>>
std::size_t draw_below(
    generator_type_ && generator,
    std::size_t bound) noexcept;

Masks to the next power of two and redraws when the value lands above the bound, which is unbiased because every value under the mask is equally likely and the surplus is simply discarded. At least half of the masked range is accepted, so fewer than two draws are expected.

Parameters

generatorinAny uniform random bit generator, as std::mt19937 and friends are.
boundinOne past the largest value that may be returned; zero and one draw nothing.

Notes

A generator narrower than the bound, like the 32-bit std::mt19937, is called repeatedly and the results concatenated, since masking a short draw could never reach the high bits.

roundup_to_multipleRounds up a value to the next multiple of a compile-time constant.
shared.hpp:2348 ↗
template <typename value_type_, value_type_ multiple_>
value_type_ roundup_to_multiple(value_type_ x) noexcept;

Parameters

xinValue to round up.

Returns

Smallest multiple of multiple_ greater than or equal to x.

algebra_keeps_firstWhether a member of the first side, at membership, is kept by algebra_.
shared.hpp:4956 ↗
template <algebra_t algebra_>
bool algebra_keeps_first(membership_t membership) noexcept;
algebra_walks_bothWhether algebra_ needs the second side walked as well as the first.
shared.hpp:4965 ↗
template <algebra_t algebra_>
bool algebra_walks_both() noexcept;

Only the two that can keep a member the first side never saw.

algebra_keeps_secondWhether a member of the second side is kept, which needs no algebra to answer.
shared.hpp:4975 ↗
bool algebra_keeps_second(membership_t membership) noexcept;

The second side is walked only to pick up what the first never had, so both algebras that reach it keep the same members - anything already shared was handed over by the first sweep.

walk_algebraHands callback every member of algebra_ over first and second.
shared.hpp:5086 ↗
template <algebra_t algebra_, typename first_type_, typename second_type_, typename callback_type_ = no_op_t>
status_t walk_algebra(
    first_type_ & first,
    second_type_ & second,
    callback_type_ && callback) noexcept;

Each side is read through a read transaction opened here, which fixes what that side shows from snapshot_k up; pass transactions to choose the instants yourself, at the cost of the nested walk compare_crossed_ falls back to.

Warnings

The two sides keep separate orders, so the pair is never one stamp, however read.

is_subsetWhether every member of first is also in second.
shared.hpp:5115 ↗
template <typename first_type_, typename second_type_>
expected<bool> is_subset(
    first_type_ & first,
    second_type_ & second) noexcept;

Settled at the first member second lacks, and the walk of first stops there.

A side with more members than the other cannot be contained in it, which is the whole answer without a walk. Both counts are read before either side is touched, so the pair is a decision about the sizes at one moment, not a size from one moment weighed against a walk from another.

is_disjointWhether the two sides share no member, on the same terms as is_subset.
shared.hpp:5147 ↗
template <typename first_type_, typename second_type_>
expected<bool> is_disjoint(
    first_type_ & first,
    second_type_ & second) noexcept;

Sharing is symmetric, so the walk takes the smaller side and probes the larger.

hash_slot_state_ofReads the state of the slot selected by mask out of both header lanes.
hash_layout.hpp:118 ↗
hash_slot_state_t hash_slot_state_of(
    hash_bucket_head_t const & head,
    hash_bucket_mask_t mask) noexcept;
hash_mark_freeDrives the slot selected by mask into the free_k state.
hash_layout.hpp:125 ↗
void hash_mark_free(
    hash_bucket_head_t & head,
    hash_bucket_mask_t mask) noexcept;
hash_mark_populatedDrives the slot selected by mask into the populated_k state.
hash_layout.hpp:131 ↗
void hash_mark_populated(
    hash_bucket_head_t & head,
    hash_bucket_mask_t mask) noexcept;
hash_mark_deletedDrives the slot selected by mask into the deleted_k state.
hash_layout.hpp:137 ↗
void hash_mark_deleted(
    hash_bucket_head_t & head,
    hash_bucket_mask_t mask) noexcept;
hash_window_slotsReads the width_ slots starting at slot window of the bucket head describes, for a probe entering them at slot entry: the live slots from entry up to the first free one.
hash_layout.hpp:545 ↗
template <std::size_t width_>
requires (hash_bucket_capacity_k % width_ == 0)
hash_window_slots_t hash_window_slots(
    hash_bucket_head_t const & head,
    std::size_t window,
    std::size_t entry) noexcept;
walk_hash_windowsWalks the probe run entered at slot entry a window at a time, handing on_match each populated slot whose key equals wanted until it halts, as hand_over reads its answer.
hash_layout.hpp:574 ↗
template <typename row_kit_type_, typename key_type_, typename offset_type_, typename on_match_type_>
void walk_hash_windows(
    hash_bucket_head_t const *headers,
    key_type_ const *keys,
    offset_type_ slots_count,
    offset_type_ entry,
    std::type_identity_t<key_type_> const & wanted,
    on_match_type_ && on_match) noexcept;

Each window takes row_kit_type_::mask_equal_width_k keys starting at a multiple of that width, compares them with one mask_equal, and masks the answer down to the live slots before the first free one. A deleted slot hides keys behind it, so only a free one ends the walk, and slots_count bounds it, so a full table ends it too.

Parameters

headersinThe headers region, indexed by bucket.
keysinThe keys region, indexed by slot, every key a window_key.
slots_countinThe slot count, a power of two, at least one window.
entryinThe home slot of wanted, already masked to the table.
wantedinThe key to find.
on_matchinReceives the slot index of each match, in probe order.
for_each_in_hash_bucketIterates over the populated slots of a bucket by scanning its population bitmap.
hash_layout.hpp:616 ↗
template <typename value_type_, typename hasher_type_, typename callback_type_>
walk_control_t for_each_in_hash_bucket(
    hash_slot_ref<value_type_, hasher_type_> & slot,
    callback_type_ && callback) noexcept;

Skips empty and deleted slots outright through countr_zero, rather than walking every one.

Parameters

slotinoutReference to a slot in the bucket. Its slot_ field is modified during iteration to point to each populated slot sequentially.
callbackinFunctor invoked for each populated slot, receiving hash_slot_ref.

Returns

Whether the bucket ran out or a halting callback stopped the walk first.

move_to_arrayMoves the objects sequence_ names out of from into a fixed-size array.
partitioned_store.hpp:24 ↗
template <typename type_, std::size_t... sequence_>
std::array<type_, sizeof...(sequence_)> move_to_array(
    type_ *from,
    std::index_sequence<sequence_...>) noexcept;
generate_array_safelyBuilds a fixed-size array from generator, or nothing at all if any element refuses.
partitioned_store.hpp:38 ↗
template <typename type_, std::size_t count_, typename generator_type_>
expected<std::array<type_, count_>> generate_array_safely(
    generator_type_ && generator) noexcept;

The generator hands back an expected per element, and a single failure destroys the prefix already built, so no half-populated array is ever observable. The scratch buffer the elements are assembled in is raw storage this function owns, so every element it constructs it also destroys - a moved-from element still has a destructor to run.

row_kit_compiledWhether this build carries kit, whatever the processor running it.
row_search.hpp:78 ↗
bool row_kit_compiled(row_kit_t kit) noexcept;
row_kit_supportedWhether this build carries kit and the running processor and operating system can execute it.
row_search.hpp:154 ↗
bool row_kit_supported(row_kit_t kit) noexcept;

Probes the processor on every call, so ask once at open or construction and keep the answer.

detect_row_kitThe newest kit this build carries and the processor can run, falling back to serial_k.
row_search.hpp:163 ↗
row_kit_t detect_row_kit() noexcept;
visit_row_kitCalls callback with an instance of the kit kit names, or of the serial kit when this build lacks it.
row_search.hpp:1463 ↗
template <typename callback_type_>
decltype(auto) visit_row_kit(
    row_kit_t kit,
    callback_type_ && callback) noexcept;

The callback is instantiated once per compiled kit, so a structure it builds calls that kit with no dispatch.

Warnings

Visiting a kit the processor cannot run faults on its first search; pass a kit row_kit_supported accepted, such as the one detect_row_kit returns.

successor_or_selfThe key right after key, or key itself when nothing orders after it.
row_search.hpp:1498 ↗
template <typename key_type_>
requires row_searchable_key<key_type_>
key_type_ successor_or_self(key_type_ key) noexcept;
count_not_aboveHow many of keys order at or below wanted; for a sorted row, the upper bound.
row_search.hpp:1510 ↗
template <row_kit row_kit_type_, typename key_type_, std::size_t extent_>
requires row_searchable_key<key_type_>
std::size_t count_not_above(
    std::span<key_type_ const, extent_> keys,
    std::type_identity_t<key_type_> wanted) noexcept;
template <row_kit row_kit_type_, std::size_t extent_>
std::size_t count_not_above(
    std::span<std::uint64_t const, extent_> high_words,
    std::span<std::uint64_t const, extent_> low_words,
    key128_t wanted) noexcept;

How many keys of a split row order at or below wanted.

count_below_sortedThe lower bound of wanted in sorted of any length.
row_search.hpp:1529 ↗
template <row_kit row_kit_type_, typename key_type_, std::size_t extent_>
requires row_searchable_key<key_type_>
std::size_t count_below_sorted(
    std::span<key_type_ const, extent_> sorted,
    std::type_identity_t<key_type_> wanted) noexcept;
template <row_kit row_kit_type_, std::size_t extent_>
std::size_t count_below_sorted(
    std::span<std::uint64_t const, extent_> high_words,
    std::span<std::uint64_t const, extent_> low_words,
    key128_t wanted) noexcept;

The lower bound of wanted in a sorted split row of any length, by 256 bytes of high words.

A row of up to about 256 bytes of keys goes to the kit whole; a longer one is first narrowed to such a window by branchless halving. Exact only when sorted is sorted.

count_below_in_rowThe lower bound of wanted in the sorted row at row, laid out by format_type_ and searched by row_kit_type_.
row_search.hpp:1565 ↗
template <typename format_type_, row_kit row_kit_type_>
std::size_t count_below_in_row(
    typename format_type_::word_t const *row,
    typename format_type_::key_t wanted) noexcept;

Constants

atomic_alignmentThe alignment atomic_ref demands of a counter updated through it.
shared.hpp:1957 ↗
std::size_t atomic_alignment;

A 64-bit integer is only 4-byte aligned by default on a 32-bit target, while atomic_ref needs 8, so a counter that is perfectly well-behaved on x86-64 becomes undefined when built for i386. Every counter the helpers below touch carries this.

size_bits_kHow many bits the size type holds here, which is not 64 everywhere.
shared.hpp:2265 ↗
std::size_t size_bits_k;
size_max_kThe largest std::size_t, which also serves as the "no such index" sentinel.
shared.hpp:2268 ↗
std::size_t size_max_k;
algebra_chunk_kHow many members a crossed walk carries out of one side before it puts that side down.
shared.hpp:4982 ↗
std::size_t algebra_chunk_k;

The only memory such a walk holds, on its own stack; a larger chunk re-seeds the walk fewer times and costs that much more of it.

hash_bucket_capacity_kNumber of slots sharing one bucket header, sized so the header fits a 64-bit atomic.
hash_layout.hpp:60 ↗
std::size_t hash_bucket_capacity_k;
every_row_kit_kEvery kit in enumerator order, for a benchmark or a log that walks them all.
row_search.hpp:58 ↗
row_kit_t every_row_kit_k[];
TypesMember types134
atomic_hash_table::mapped_typemapped_t
atomic_hash_table::value_typevalue_t
atomic_hash_table::owned_value_typetypename layout_t::value_copy_t
atomic_hash_table::size_typeoffset_t
atomic_hash_table::hasherhasher_t
atomic_hash_table::key_equalequals_t
atomic_hash_table::allocator_typeallocator_t
atomic_hash_table::storage_typestorage_t
atomic_hash_table::is_transactionalstd::false_type
atomic_intrusive_stack::record_trecord_type_
basic_avl_node::value_tvalue_type_
basic_avl_node::comparator_tcomparator_type_
basic_avl_node::height_tstd::ptrdiff_t
basic_avl_node::node_tbasic_avl_node
basic_avl_node::search_tbinary_node_search<node_t, comparator_t>
basic_avl_tree::node_tbasic_avl_node<value_type_, comparator_type_>
basic_avl_tree::node_typenode_t
basic_avl_tree::allocator_tstd::allocator_traits<allocator_type_>::template rebind_alloc<node_t>
basic_avl_tree::allocator_typeallocator_t
basic_avl_tree::comparator_tcomparator_type_
basic_avl_tree::comparator_typecomparator_t
basic_avl_tree::value_tvalue_type_
basic_avl_tree::value_typevalue_t
basic_avl_tree::key_typekey_t
basic_avl_tree::mapped_typemapped_t
basic_avl_tree::is_associativestd::bool_constant<is_mapping<value_t>>
basic_avl_tree::reverse_iteratorreversed_iterator<iterator>
basic_avl_tree::const_reverse_iteratorreversed_iterator<const_iterator>
basic_fixed_pool::value_tvalue_type_
basic_fixed_pool::index_tindex_type_
basic_fixed_pool::allocator_tallocator_type_
basic_flat_set::value_tvalue_type_
basic_flat_set::value_typevalue_t
basic_flat_set::key_typekey_t
basic_flat_set::mapped_typemapped_t
basic_flat_set::comparator_tcomparator_type_
basic_flat_set::comparator_typecomparator_t
basic_flat_set::kit_trow_kit_type_
basic_flat_set::allocator_tallocator_type_
basic_flat_set::allocator_typeallocator_t
basic_flat_set::is_associativestd::false_type
basic_growing_pool::value_tvalue_type_
basic_growing_pool::index_tindex_type_
basic_growing_pool::allocator_tallocator_type_
basic_hash_index::key_tkey_type_
basic_hash_index::index_tindex_type_
basic_hash_index::hasher_thasher_type_
basic_hash_index::allocator_tallocator_type_
basic_hash_table::key_typekey_t
basic_hash_table::mapped_typemapped_t
basic_hash_table::result_typeinsert_result_t
basic_hash_table::value_typevalue_t
basic_hash_table::size_typeoffset_t
basic_hash_table::difference_typestd::ptrdiff_t
basic_hash_table::key_compareequals_t
basic_hash_table::allocator_typeallocator_t
basic_hash_table::referencevalue_t
basic_hash_table::const_referencevalue_t
basic_hash_table::iteratoriterator_t
basic_hash_table::const_iteratorconst_iterator_t
basic_hash_table::hasherhasher_t
basic_hash_table::key_equalequals_t
basic_hash_table::storage_typestorage_t
basic_hash_table::is_transactionalstd::false_type
basic_ring::value_tvalue_type_
basic_ring::value_typevalue_t
basic_ring::allocator_tallocator_type_
basic_ring::allocator_typeallocator_t
basic_vector::value_tvalue_type_
basic_vector::value_typevalue_t
basic_vector::allocator_tallocator_type_
basic_vector::allocator_typeallocator_t
basic_wb_node::value_tvalue_type_
basic_wb_node::comparator_tcomparator_type_
basic_wb_node::augmentation_taugmentation_type_
basic_wb_node::size_tstd::size_t
basic_wb_node::node_tbasic_wb_node
basic_wb_node::search_tbinary_node_search<node_t, comparator_t>
basic_wb_tree::value_tvalue_type_
basic_wb_tree::value_typevalue_t
basic_wb_tree::comparator_tcomparator_type_
basic_wb_tree::comparator_typecomparator_t
basic_wb_tree::augmentation_taugmentation_type_
basic_wb_tree::node_tbasic_wb_node<value_t, comparator_t, augmentation_t>
basic_wb_tree::node_typenode_t
basic_wb_tree::node_allocator_tnode_allocator_type_
basic_wb_tree::wb_tree_tbasic_wb_tree
basic_wb_tree::size_tstd::size_t
basic_wb_tree::allocator_tnode_allocator_type_
basic_wb_tree::allocator_typeallocator_t
basic_wb_tree::key_typekey_t
basic_wb_tree::mapped_typemapped_t
immutable_b_tree::value_typevalue_t
immutable_b_tree::key_typekey_t
immutable_b_tree::mapped_typemapped_t
immutable_b_tree::is_associativestd::bool_constant<is_mapping<value_t>>
immutable_b_tree::format_trow_format<key_t, keys_per_row_>
immutable_b_tree::word_ttypename format_t::word_t
immutable_b_tree::kit_trow_kit_type_
immutable_b_tree::allocator_tallocator_type_
immutable_b_tree::allocator_typeallocator_t
immutable_elias_fano::value_tvalue_type_
immutable_elias_fano::value_typevalue_t
immutable_elias_fano::allocator_tallocator_type_
immutable_elias_fano::allocator_typeallocator_t
immutable_packed_array::value_tvalue_type_
immutable_packed_array::value_typevalue_t
immutable_packed_array::allocator_tallocator_type_
immutable_packed_array::allocator_typeallocator_t
immutable_packed_array_view::value_tvalue_type_
immutable_packed_array_view::value_typevalue_t
immutable_splus_tree::value_typevalue_t
immutable_splus_tree::key_typekey_t
immutable_splus_tree::mapped_typemapped_t
immutable_splus_tree::is_associativetypename view_t::is_associative
immutable_splus_tree::format_ttypename view_t::format_t
immutable_splus_tree::word_ttypename view_t::word_t
immutable_splus_tree::kit_trow_kit_type_
immutable_splus_tree::allocator_tallocator_type_
immutable_splus_tree::allocator_typeallocator_t
immutable_splus_tree_view::value_typevalue_t
immutable_splus_tree_view::key_typekey_t
immutable_splus_tree_view::mapped_typemapped_t
immutable_splus_tree_view::is_associativestd::bool_constant<is_mapping<value_t>>
immutable_splus_tree_view::format_trow_format<key_t, keys_per_row_>
immutable_splus_tree_view::word_ttypename format_t::word_t
immutable_splus_tree_view::kit_trow_kit_type_
hash_storage::layout_thash_layout_for<value_type_, hasher_type_>
hash_storage::key_ttypename layout_t::key_t
hash_storage::mapped_storage_ttypename layout_t::mapped_storage_t
hash_storage::offset_ttypename layout_t::offset_t
hash_storage::allocator_tallocator_type_
hash_storage::slot_ref_thash_slot_ref<value_type_, hasher_type_>
intrusive_list::index_tindex_type_