C++ Dictionary Overview
Dictionary handling in C++ traditionally relies on std::unordered_map,
which provides convenient automatic memory management but comes with
limitations in specialized environments. The standard library implementation
uses dynamic allocation with opaque memory policies, making it unsuitable for
embedded systems, real-time applications, or projects requiring deterministic
memory behavior and MISRA C++ compliance.
The C++ Dictionary module in this library provides a lightweight,
allocator-aware generic hash dictionary implemented in C++ and declared in
dict.hpp. Unlike std::unordered_map, this container:
Integrates directly with custom allocator hierarchies (heap, arena, buddy, slab)
Explicitly tracks entry count, bucket occupancy, and the owning allocator
Enforces initialization through a factory pattern preventing uninitialized dicts
Provides automatic cleanup via RAII with custom deleters
Returns explicit success/error state using the
Expected<T>patternSupports deep copy, merge, and templated iteration via
foreach()
The only dependencies this module has within the CSalt library are the
allocator.hpp and error.hpp files, making it suitable for integration
into larger systems while maintaining minimal coupling.
Allocator Integration
By integrating directly with the Allocator base class defined in
allocator.hpp, the dictionary container supports multiple allocation
strategies without changing the public API:
HeapAllocator — Standard heap allocation via
operator newArenaAllocator — Sequential bump allocation with bulk deallocation
BuddyAllocator — Power-of-two block allocation with coalescing
SlabAllocator — Fixed-size object pools for uniform allocations
Custom allocators — Any user-defined class inheriting from
Allocator
Both the Dict struct itself and every internal node (key copy plus inline
value buffer) are allocated through the provided allocator, enabling fully
deterministic memory behaviour suitable for embedded, real-time, or
safety-regulated environments. The user can also develop their own allocators
as long as they conform to the Allocator base class.
Recommended Allocators
Different allocators are appropriate depending on how dictionaries are used and how frequently elements are inserted and removed:
HeapAllocator
Best for:
general-purpose hash maps
development and testing
workloads with unpredictable insertion and removal patterns
The heap allocator is flexible and easy to debug, making it the default choice for most use cases.
ArenaAllocator
Best for:
build-once dictionaries
read-only lookup tables
configuration or initialization data
This is highly efficient when the dictionary is constructed once and not modified or individually freed.
BuddyAllocator
Best for:
dynamic dictionaries with frequent insertions and deletions
systems requiring controlled fragmentation
long-running applications
Buddy allocation helps manage fragmentation when many nodes are allocated and returned over time.
PoolAllocator / SlabAllocator
Best for:
high-frequency insert/remove workloads
uniform key/value sizes
performance-critical systems
Dictionary nodes are typically uniform in size, making them an excellent match for pool or slab allocators. This can significantly reduce allocation overhead and improve cache locality.
Factory Pattern and RAII
Unlike traditional constructors, dictionary creation uses a static factory
function Dict::init() that returns Expected<Dict<K,V>*>. This design:
Prevents creation of uninitialized or invalid dictionaries
Provides explicit error handling at the point of creation
Enables proper two-phase initialization (struct allocation + bucket array allocation)
Returns typed error objects (
ArgumentError,MemoryError) with descriptive messages
Memory cleanup is automatic through the DictDeleter custom deleter, which
works seamlessly with UniquePtr to provide RAII semantics:
// Automatic cleanup when UniquePtr goes out of scope
{
cslt::HeapAllocator allocator;
auto result = cslt::Dict<int, float>::init(8, true, allocator);
if (result.hasValue()) {
cslt::UniquePtr<cslt::Dict<int, float>,
cslt::DictDeleter<int, float>> d(result.value());
d->insert(1, 1.1f);
d->insert(2, 2.2f);
d->insert(3, 3.3f);
} // Dict, all nodes, and all key copies automatically freed here
}
Template Type Requirements
Dict<K, V> is parameterised on two independent types.
Key type K must satisfy std::is_trivially_copyable_v<K>. This
constraint is enforced at compile time via static_assert. Raw-byte
hashing operates on sizeof(K) bytes, which is only correct when the
object representation is the complete value representation — a guarantee
provided by trivial copyability. Any attempt to instantiate Dict with a
non-trivially-copyable key type will produce a clear compile-time error.
Key equality is resolved at compile time using a C++17 detection trait:
If
Kprovidesoperator==, it is used for equality comparison.Otherwise,
memcmpoversizeof(K)bytes is used as a fallback.
Value type V must be default-constructible, copy-constructible, and
destructible. Default-constructibility is required because get() and
pop() declare a local Expected<V> which default-constructs V
before a value is available. Copy-constructibility is required because values
are copy-constructed into the node’s inline buffer on insert and update.
Destructibility is required because the destructor is called explicitly when
a node is freed.
Requirement |
Reason |
|---|---|
K trivially copyable |
Raw-byte hashing and memcmp equality |
V default-constructible |
Expected<V> default constructor |
V copy-constructible |
Placement-new into node inline buffer |
V destructible |
Explicit destructor call on node free |
Hash Function
The dictionary uses a MurmurHash3-inspired hash function operating on the
raw bytes of the key. The function processes the key in 4-byte blocks with
a finalisation mixing step, producing well-distributed 32-bit hashes
suitable for general-purpose use. The hash seed is fixed at 0xdeadbeef.
Bucket indices are computed as hash(key) % bucket_count, where
bucket_count is always a power of two. Because the bucket count is a
power of two a bitwise AND could replace the modulo; the modulo form is
retained for clarity.
Growth Strategy
When growth is enabled (growth == true in init()), the bucket array
is resized automatically when the load factor exceeds 0.75. The growth
strategy is tiered to limit memory overhead at large sizes:
Current bucket count |
New bucket count |
|---|---|
< 4,096 buckets |
2× current |
≥ 4,096 buckets |
current + 1,024 (linear increment) |
The new bucket count is always rounded up to the next power of two. All existing nodes are rehashed into the new bucket array in a single pass; the old bucket array is freed via the allocator immediately afterwards.
When growth is disabled (growth == false), insert() returns
false when hash_size() >= bucket_count(), leaving the caller in
full control of memory allocation.
Node Layout
Each key-value pair is stored in a single chain of nodes within a bucket. A node consists of:
A node header (
nextpointer +key_datapointer) allocated as one block ofsizeof(Node) + sizeof(V)bytes.A key copy — a separate allocation of
sizeof(K)bytes holding amemcpyof the caller’s key. This ensures that the key’s lifetime is tied to the node, not the caller’s stack or buffer.An inline value buffer — the
sizeof(V)bytes immediately following the node header in the same allocation, populated via placement new.
This layout means each insert performs exactly two allocator calls (node + key), and each remove performs exactly two frees.
Iteration
The foreach() method accepts any callable with signature
void(const K&, const V&), including lambdas, functors, and plain function
pointers:
cslt::HeapAllocator allocator;
auto r = cslt::Dict<int, float>::init(8, true, allocator);
if (!r.hasValue()) return;
cslt::UniquePtr<cslt::Dict<int, float>,
cslt::DictDeleter<int, float>> d(r.value());
d->insert(1, 1.1f);
d->insert(2, 2.2f);
d->insert(3, 3.3f);
// Lambda accumulator
float total = 0.0f;
d->foreach([&total](const int& key, const float& value) {
total += value;
});
Traversal order is bucket order, which is neither insertion order nor key order. The callback must not insert, update, remove, or clear entries during traversal.
Copy and Merge
Two utility factory methods are provided for building new dictionaries from existing ones.
copy() creates a fully independent deep copy. All nodes are
copy-constructed into a fresh bucket array of the same capacity. A
convenience single-argument overload reuses the source dictionary’s own
allocator:
// Two-argument form — explicit allocator
auto cr = cslt::Dict<int, float>::copy(*src, allocator);
// One-argument form — uses src's own allocator
auto cr = cslt::Dict<int, float>::copy(*src);
merge() combines two dictionaries into a new one. All entries from the
first source are inserted first, then entries from the second source are
processed according to the overwrite flag:
// overwrite == true: second dict's values win on key conflicts
// overwrite == false: first dict's values are preserved on conflicts
auto mr = cslt::Dict<int, float>::merge(*a, *b, true, allocator);
Neither source dictionary is modified by merge().
String Keys
The most common use case for a dictionary is mapping string keys to values.
cslt::String cannot be used directly as a key because its private
destructor and deleted copy operations make it non-trivially copyable — a
requirement enforced by Dict’s static_assert.
cslt::StringKey<N> solves this cleanly. It copies the string content into
a fixed-size stack buffer of N bytes (including the null terminator),
producing a type that is trivially copyable, zero-cost to hash, and requires
no heap allocation. Strings longer than N-1 characters are silently
truncated, so choose N based on the longest key you expect to store.
Type |
Recommended use |
|---|---|
StringKey<32> |
Short identifiers, sensor names, config |
StringKey<64> |
Dictionary words, variable names |
StringKey<128> |
Sentences, file names |
StringKey<256> |
File paths, long descriptors |
A type alias keeps call sites concise:
using Key64 = cslt::StringKey<64>;
Basic string key usage:
#include "dict.hpp"
using Key64 = cslt::StringKey<64>;
cslt::HeapAllocator alloc;
auto r = cslt::Dict<Key64, float>::init(8, true, alloc);
if (!r.hasValue()) return;
cslt::UniquePtr<cslt::Dict<Key64, float>,
cslt::DictDeleter<Key64, float>> d(r.value());
// Insert using C-string literals
d->insert(Key64{"pi"}, 3.14159f);
d->insert(Key64{"euler"}, 2.71828f);
d->insert(Key64{"phi"}, 1.61803f);
// Look up by C-string literal
auto gr = d->get(Key64{"pi"});
if (gr.hasValue()) {
float val = gr.value(); // 3.14159f
}
bool found = d->has_key(Key64{"euler"}); // true
bool missing = d->has_key(Key64{"zero"}); // false
Constructing a key from a cslt::String:
#include "dict.hpp"
#include "string.hpp"
using Key64 = cslt::StringKey<64>;
cslt::HeapAllocator alloc;
auto r = cslt::Dict<Key64, float>::init(8, true, alloc);
if (!r.hasValue()) return;
cslt::UniquePtr<cslt::Dict<Key64, float>,
cslt::DictDeleter<Key64, float>> d(r.value());
d->insert(Key64{"temperature"}, 98.6f);
// Build a lookup key from a runtime cslt::String
auto rs = cslt::String::init("temperature", 0, alloc);
if (rs.hasValue()) {
cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(rs.value());
auto gr = d->get(Key64{*s}); // construct key from String
if (gr.hasValue()) {
float temp = gr.value(); // 98.6f
}
}
Iterating over string-keyed entries:
d->foreach([](const Key64& key, const float& value) {
// key.c_str() returns the null-terminated string
// key.size() returns the logical length
});
Updating and removing string keys:
d->update(Key64{"pi"}, 3.14f); // overwrite existing value
auto pr = d->pop(Key64{"euler"}); // remove and return value
if (pr.hasValue()) {
float e = pr.value(); // 2.71828f
}
StringKey Class
- template<size_t N>
class StringKeyFixed-size trivially copyable string key for use with Dict<K, V>
cslt::Stringcannot be used directly as a Dict key because its private destructor and deleted copy operations make it non-trivially-copyable — a requirement enforced by Dict’sstatic_assert.StringKey<N>solves this by copying the string content into a fixed-size stack buffer ofNbytes (including the null terminator), making the type trivially copyable and zero-cost to hash.The template parameter
Nis the total buffer size including the null terminator, so aStringKey<32>stores up to 31 printable characters. Strings longer thanN-1characters are silently truncated.
operator==usesstd::strncmpso Dict’s equality detection trait picks it up automatically — no custom comparator is needed.Recommended sizes:
StringKey<32>— short identifiers, sensor names, config keys
StringKey<64>— typical dictionary words, variable names
StringKey<128>— sentences, file names
StringKey<256>— file paths, long descriptors// Type alias to keep call sites concise using Key64 = cslt::StringKey<64>; cslt::HeapAllocator alloc; auto r = cslt::Dict<Key64, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<Key64, float>, cslt::DictDeleter<Key64, float>> d(r.value()); // Insert using a string literal d->insert(Key64{"pi"}, 3.14159f); d->insert(Key64{"euler"}, 2.71828f); // Look up by string literal auto gr = d->get(Key64{"pi"}); if (gr.hasValue()) { float val = gr.value(); // 3.14159f } // Construct a key from a cslt::String auto rs = cslt::String::init("euler", 0, alloc); if (rs.hasValue()) { cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(rs.value()); bool found = d->has_key(Key64{*s}); // true }
- Template Parameters:
N – Total buffer size in bytes, including the null terminator. Must be >= 2.
Public Functions
- inline explicit StringKey(const char *s) noexcept
Construct from a null-terminated C-string.
- Parameters:
s – Source string. If nullptr, the key is initialised to an empty string. If longer than N-1 characters the content is silently truncated.
- template<typename StringT>
inline explicit StringKey(const StringT &s) noexceptConstruct from a cslt::String.
Note
This constructor is only available when string.hpp has been included before dict.hpp. If you do not use cslt::String as a source, include only dict.hpp and construct from a C-string literal instead.
- Parameters:
s – Source String. If its content is longer than N-1 characters it is silently truncated.
- inline bool operator==(const StringKey<N> &other) const noexcept
Equality comparison — required by Dict’s key equality detection.
- Returns:
true if both keys hold the same null-terminated string content
- inline const char *c_str() const noexcept
Return the stored string as a C-string pointer.
- inline size_t size() const noexcept
Return the logical length of the stored string (excludes null terminator)
Dict Class
- template<typename K, typename V>
class DictAllocator-backed generic hash dictionary mapping keys of type K to values of type V.
Implements a chained hash table using the MurmurHash3-inspired hash function from the companion C implementation. Both the Dict object and every internal node are allocated through the provided Allocator, enabling deterministic memory behaviour across heap, arena, buddy, and slab allocators.
Key features:
Template parameters K and V decouple key and value types completely
K must be trivially copyable; keys are hashed over their raw bytes
Key equality uses operator== when available, memcmp as fallback
V may be any copy-constructible, destructible type
Optional automatic growth when load factor exceeds 0.75
Factory pattern via init() prevents uninitialised instances
RAII cleanup through DictDeleter and UniquePtr
foreach() iteration accepts any callable (lambda, functor, function pointer)
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (r.hasValue()) { cslt::UniquePtr<cslt::Dict<int,float>, cslt::DictDeleter<int,float>> d(r.value()); d->insert(42, 3.14f); auto val = d->get(42); // Expected<float> auto popped = d->pop(42); // Expected<float> }
- Template Parameters:
Public Functions
- inline bool insert(const K &key, const V &value) noexcept
Insert a new key-value pair.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); bool ok = d->insert(42, 3.14f); // ok == true bool dup = d->insert(42, 9.99f); // dup == false — duplicate key rejected
- Parameters:
key – Key to insert. Must not already exist in the dict.
value – Value to associate with the key. Copy-constructed into the node’s inline buffer.
- Returns:
true on success; false if the key already exists, allocation fails, or the dict is full and growth is disabled.
- inline bool update(const K &key, const V &value) noexcept
Overwrite the value of an existing key.
The key is not re-hashed and no allocation is performed — only the stored value is replaced via destroy-then-copy-construct in place.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(42, 3.14f); bool ok = d->update(42, 6.28f); // ok == true, value is now 6.28f bool nf = d->update(99, 1.00f); // nf == false, key 99 does not exist
- Parameters:
key – Key to update. Must already exist in the dict.
value – New value to store.
- Returns:
true on success; false if the key is not found.
- inline Expected<V> pop(const K &key) noexcept
Remove a key-value pair and return the removed value.
The node is unlinked from its bucket chain and freed. The value is copy-constructed into the Expected before the node is destroyed, so the caller always receives the value regardless of what type V is.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(42, 3.14f); d->insert(99, 9.99f); auto pr = d->pop(42); if (pr.hasValue()) { float v = pr.value(); // v == 3.14f; key 42 no longer in dict } auto missing = d->pop(7); // missing.hasValue() == false — key 7 was never inserted
- Parameters:
key – Key to remove.
- Returns:
Expected<V> containing the value on success, or an OutOfBoundsError if the key is not found.
- inline Expected<V> get(const K &key) const noexcept
Return a copy of the value associated with a key.
The dict is not modified. The returned Expected owns a copy of the value; mutations to the copy do not affect the stored value.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(42, 3.14f); auto gr = d->get(42); if (gr.hasValue()) { float v = gr.value(); // v == 3.14f } auto missing = d->get(99); // missing.hasValue() == false
- Parameters:
key – Key to look up.
- Returns:
Expected<V> containing a copy of the value on success, or an OutOfBoundsError if the key is not found.
- inline const V *get_ptr(const K &key) const noexcept
Return a read-only pointer directly into the node’s value slot.
The pointer remains valid until the next mutation of the dict (insert, update, pop, clear, or any operation that triggers a resize). The caller must not free or write through it.
Prefer get() when a copy is acceptable. Use get_ptr() only when avoiding the copy is important and the pointer lifetime can be guaranteed.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(42, 3.14f); const float* ptr = d->get_ptr(42); // ptr != nullptr if (ptr) { float v = *ptr; // v == 3.14f — zero-copy read } const float* missing = d->get_ptr(99); // missing == nullptr
- Parameters:
key – Key to look up.
- Returns:
Const pointer to the value on success, nullptr if not found.
- inline bool has_key(const K &key) const noexcept
Test whether a key exists without retrieving its value.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(42, 3.14f); bool found = d->has_key(42); // true bool missing = d->has_key(99); // false
- Parameters:
key – Key to test.
- Returns:
true if the key exists, false otherwise.
- inline void clear() noexcept
Remove all entries without freeing the Dict or its bucket array.
Every node (key copy + inline value) is destroyed and freed via the allocator. The bucket array is retained and zeroed so the Dict can be reused immediately without a further allocation. size() and hash_size() are reset to 0; bucket_count() is unchanged.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(1, 1.1f); d->insert(2, 2.2f); // d->hash_size() == 2 d->clear(); // d->hash_size() == 0, d->is_empty() == true // d->bucket_count() is unchanged — the bucket array is reused d->insert(3, 3.3f); // can insert immediately after clear
- template<typename Func>
inline bool foreach(Func fn) const noexceptCall
fnonce for every key-value pair in the dict.Traversal order is bucket order (not insertion order). The callback receives const references to both the key and the value.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(1, 1.1f); d->insert(2, 2.2f); d->insert(3, 3.3f); // Lambda — accumulate the sum of all values float total = 0.0f; d->foreach([&total](const int& key, const float& value) { total += value; }); // total == 6.6f (approximately) // File-scope function pointer static void print_entry(const int& key, const float& value) { // log key and value } d->foreach(print_entry);
- Template Parameters:
Func – Callable with signature
void(const K&, const V&). Lambdas, functors, and plain function pointers are all accepted. The callback must not insert, update, remove, or clear entries during traversal.- Parameters:
fn – Callback invoked for each entry.
- Returns:
true on success; false if the dict is uninitialised.
- inline size_t size() const noexcept
Number of occupied buckets (chains with at least one entry)
This is the number of distinct hash slots in use, not the total number of key-value pairs. Multiple keys may hash to the same bucket and form a chain; each such chain counts as one occupied bucket. Use hash_size() for the total number of pairs.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(1, 1.0f); d->insert(2, 2.0f); size_t occ = d->size(); // <= 2 (may be 1 if both keys hash to same bucket)
- inline size_t hash_size() const noexcept
Total number of key-value pairs stored.
Incremented by insert() and decremented by pop(). Reset to 0 by clear(). This is the value to use when you need to know how many distinct keys are in the dict.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(1, 1.0f); d->insert(2, 2.0f); size_t n = d->hash_size(); // n == 2
- inline size_t bucket_count() const noexcept
Number of buckets allocated (always a power of two)
This is the current size of the internal bucket array. It grows automatically when growth is enabled and the load factor exceeds 0.75. The initial value is the smallest power of two >= the capacity passed to init(), with a minimum of 8.
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(10, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); size_t bc = d->bucket_count(); // bc == 16 (next power of two >= 10)
- inline bool is_empty() const noexcept
true if no key-value pairs are stored
Equivalent to hash_size() == 0. Returns true on a freshly initialised dict and after clear().
cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); bool empty_before = d->is_empty(); // true d->insert(1, 1.0f); bool empty_after = d->is_empty(); // false d->clear(); bool empty_again = d->is_empty(); // truePublic Static Functions
- static inline Expected<Dict<K, V>*> init(size_t capacity, bool growth, Allocator &allocator) noexcept
Allocate and initialise a new Dict.
// Create an int->float dictionary with an initial capacity of 16 buckets // and automatic growth enabled. The UniquePtr owns the dict and frees // it automatically when it goes out of scope. cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(16, true, alloc); if (!r.hasValue()) { // handle error — r.error() describes the failure return; } cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> d(r.value()); d->insert(1, 1.1f); d->insert(2, 2.2f); // d is freed here when it leaves scope
- Error conditions
capacity == 0 (ArgumentError)
Allocation of the Dict struct fails (MemoryError)
Allocation of the bucket array fails (MemoryError)
- Parameters:
- Returns:
Expected<Dict<K,V>*> on success, or a MemoryError / ArgumentError
- static inline Expected<Dict<K, V>*> copy(const Dict<K, V> &src, Allocator &allocator) noexcept
Create a deep copy of
srcusing a caller-supplied allocator.All key-value pairs are rehashed into a new bucket array of the same capacity as
src. Nodes are copy-constructed individually so the copy is fully independent of the source.cslt::HeapAllocator alloc; // Build the source dictionary auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> src(r.value()); src->insert(1, 1.1f); src->insert(2, 2.2f); // Deep-copy into a second dictionary backed by the same allocator auto cr = cslt::Dict<int, float>::copy(*src, alloc); if (!cr.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> dst(cr.value()); // src and dst are independent — mutating one does not affect the other dst->insert(3, 3.3f);
- Parameters:
- Returns:
Expected<Dict<K,V>*> on success, or a MemoryError on allocation failure
- static inline Expected<Dict<K, V>*> copy(const Dict<K, V> &src) noexcept
Create a deep copy using the source dict’s own allocator.
Convenience overload that forwards to copy(src, allocator) using the allocator already associated with
src.cslt::HeapAllocator alloc; auto r = cslt::Dict<int, float>::init(8, true, alloc); if (!r.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> src(r.value()); src->insert(10, 9.9f); // Copy using src's own allocator — no need to pass alloc explicitly auto cr = cslt::Dict<int, float>::copy(*src); if (!cr.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> dst(cr.value());
- Parameters:
src – Dict to copy from
- Returns:
Expected<Dict<K,V>*> on success, or a MemoryError
- static inline Expected<Dict<K, V>*> merge(const Dict<K, V> &a, const Dict<K, V> &b, bool overwrite, Allocator &allocator) noexcept
Merge two dicts into a new dict.
All entries from
aare inserted first. Entries frombare then processed:
If the key does not exist in the result it is inserted.
If the key exists and
overwriteis true the value is replaced.If the key exists and
overwriteis false the original value is kept.
aandbare not modified.cslt::HeapAllocator alloc; // Build the first source dictionary auto ra = cslt::Dict<int, float>::init(8, true, alloc); if (!ra.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> a(ra.value()); a->insert(1, 1.0f); a->insert(2, 2.0f); // Build the second source dictionary — key 2 conflicts with a auto rb = cslt::Dict<int, float>::init(8, true, alloc); if (!rb.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> b(rb.value()); b->insert(2, 99.0f); // conflict — same key as in a b->insert(3, 3.0f); // Merge: b wins on conflicts (overwrite == true) // Result contains: {1->1.0, 2->99.0, 3->3.0} auto mr = cslt::Dict<int, float>::merge(*a, *b, true, alloc); if (!mr.hasValue()) return; cslt::UniquePtr<cslt::Dict<int, float>, cslt::DictDeleter<int, float>> merged(mr.value());
- Parameters:
a – First source dict
b – Second source dict
overwrite – If true,
b'svalues win on key conflictsallocator – Allocator for the new dict
- Returns:
Expected<Dict<K,V>*> on success, or a MemoryError on allocation failure