Elements: identity, hashing, order, indices¶
What a container needs of its elements comes from three function objects, with nothing in namespace std:
-
template<typename T>
struct Hash¶ hash_t operator()(const T&) const: a deterministic 64-bit hash (scalars as themselves, Boost.ContainerHash mixing for composites). Given for arithmetic types, enums, strings and string views, the standard’s composites (pair,tuple,array,vector,set,map,optional,variant,span), anyIdentifiabletype,urd::index, the views (urd::index_viewand the record views), the flat containers ofurd/elements/flat.hpp, any range whose elements in order are its value (urd::value_range<R> = true: hashed, compared and ordered as its elements), and Stores whose contents fix their order (sorted,canonical, sequences). There is deliberately no fallback tostd::hash.
-
template<typename T>
struct EqualTo¶ bool operator()(const T&, const T&) const: equality agreeing withHash; for floating point, NaN equals NaN. Given for any iterable Store (equal contents, as its==compares them).
-
template<typename T>
struct Less¶ bool operator()(const T&, const T&) const: a strict weak order agreeing withEqualTo, lexicographic over members and ranges. Given for Stores iterated in an order their request fixes (sorted,insertion_ordered, sequences), so such a Store can be a sorted key.
Which of the three a Store has is decided by its request, never by its representation: a Store named as another Store’s element is resolved before its representation is defined.
A type names its identity once and is then hashable, comparable and ordered, in member order:
-
template<auto... Ms>
struct Members¶ The identifying members of a record, as member pointers.
struct Edge
{
Vertex from, to;
static constexpr auto identifying_members() noexcept { return Members<&Edge::from, &Edge::to> {}; }
};
using Edges = Store<set<Edge>>; // hashed by (from, to)
-
template<typename T>
concept Identifiable¶ TisMemberIdentified, or itsidentifying_members()gives its identity as a value (a view gives a tuple). Hash, EqualTo and Less ofTare those of that identity.
-
template<typename T>
concept orderable¶ LessandEqualToapply toT: what asortedkey or aby_priorityelement needs.
-
template<typename K>
struct index_of¶ How a key is an index 0..n-1:
is_index(key)(a negative integer, orurd::index’s none, is no index, and a lookup of one finds nothing),to_index(key)andfrom_index(std::size_t). Given for non-negative integers, enums, types identified by one integral member, andurd::index; specialize it for others.
Prehashed keys¶
A key hashed once can be looked up, and inserted into an interner, without being hashed again: the standard’s way to
pass a precomputed hash. C++20 had dedicated overloads for it (P0920) and dropped them (P1661) for heterogeneous
lookup by a key that carries its hash, which is what urd offers; there are no *_with_hash members.
-
template<typename T>
struct prehashed¶ const T& keyandhash_t hash,Hash’s ofkey. It refers tokey, which must outlive it.
-
template<typename T>
prehashed<T> prehash(const T &key)¶ keywith its hash. Not of a temporary (that overload is deleted): the result would refer to it once it is gone.
A hashed Store’s find, contains and count take a prehashed<T> wherever they take a T (the hash
sets and maps, the parallel ones, the positional sets, the record, value and arena interners), and an interner’s
insert(key) and insert(key, make) do too. An interner hashes a new element once, its insert included: the
lookup’s hash goes into the table with the new index.
Indices and views¶
-
template<typename T>
struct index¶ The index of a
Tin the Store that numbersT’s (urd/elements/index.hpp): a positional set mints one on first insert, and a pool’s handles are indices. One 32-bit integer (value) with anonevalue (is_none()), a different type perTthat converts to no other, hashable, orderable, and a dense key. Write iturd::index: the C library declares a functionindexin the global namespace.-
value_type value¶
The number;
none_valueby default.
-
static constexpr value_type none_value¶
The
nonevalue: the largestvalue_type. A default-builtindexis none.
-
bool is_none() const noexcept¶
-
value_type value¶
-
struct layer¶
The number of a Store that hands out indices for views (a pool of records, a positional set): such a Store is built from it first,
Atoms(urd::layer { 3 }), and answers it aslayer(). Who builds the Store gives it, and Stores whose views must differ get different numbers; a layer gets its own, as its parent does.-
std::size_t number¶
-
std::size_t number¶
A T is read through a context C that resolves the indices in its record (resolves), by one of
four views (urd/elements/view.hpp), which differ only in how they hold the record. Each borrows its context as a
const C*, which must outlive it, and has the same surface: context(), data(), member<&T::m>(), the
accessors of its view_mixin, value_type (T), and an identity (what identifies the record, and
the context’s number) that ==, <=>, EqualTo, Hash and Less compare by. A positional set and a pool
of records resolve their own type; any type that gives the record, its number and the view of an index serves.
-
template<typename T, typename C = typename context_of<T>::type>
class index_view¶ Holds an
index<T>, read through the context ascontext[index](aT, or its stored recorddata<T>) and identified by it: what a context makes of its indices,context.view(index). Two views of one element from one context are equal and hash equally, wherever they were made; views from differently numbered contexts are not.
-
template<typename T, typename C = typename context_of<T>::type, typename R = data<T>>
class borrowed_view¶ Points at a record
R(data<T>, abuilder<T>, or another form ofT) held outside the context, which must outlive the view; read and identified by the record. Whatresolve()makes of a record or builder held in place (never of a temporary).
-
template<typename T, typename C = typename context_of<T>::type, typename R = data<T>>
class owning_view¶ Holds a record
Rby value: one computed rather than stored. Read and identified by the record.
Shares a record
R(std::shared_ptr<const R>), released with the last copy. Read and identified by the record.
What all four give:
- const C &context() const noexcept¶
- decltype(auto) data() const¶
The record: for an index_view as the context holds it, else the record held.
- template<auto Member>
decltype(auto) get() const¶A member of the record resolved through the context (
resolve()): anindex<U>becomes theindex_view<U, C>the context makes, pointing at the layer that owns the element; a range of indices a range of views; a variant the variant of its alternatives resolved; an optional the optional of its value resolved; a pair the pair of its members resolved; a stored record held in the record its borrowed_view; anything else stays as it is.
-
template<typename C, typename T>
concept resolves¶ context[index<T>]is convertible toconst T&, or is the stored recorddata<T>(the slot ofurd/elements/data.hpp, which must be Serializable);context.layer()is its number;context.view(index<T>)gives theindex_view<T, C>of the index, through the context that owns it (a layer points it at the layer that owns the element; a context made of several Stores writes one per type).
-
template<typename M, typename C>
decltype(auto) resolve(M &&member, const C &context)¶ A record’s member as a view sees it:
resolution<M>::of(member, context), the member passed on as given (so that a record or builder, which its resolution borrows, is refused as a temporary); seeindex_view::get().
-
template<typename M>
struct resolution¶ How a member of type
Mresolves: as it is, unless specialized. Given for indices (index<U>: the context’s view), ranges of indices (a range of views), and variants, optionals and pairs, std’s and cista’s alike (their contents resolved;urd/elements/view.hpp, by the conceptsvariant_value,optional_valueandpair_value). A new member type resolves by specializing it.
-
template<std::ranges::input_range R, std::copy_constructible F>
class transformed_range¶
-
template<std::ranges::input_range R, typename F>
transformed_range<std::remove_cvref_t<R>, std::decay_t<F>> transform(R &&range, F &&function)¶ rangeread throughfunction: each elementxread asfunction(x), a borrowedstd::views::transform. It holdsrange’s iterators and the function, and so do its iterators: unlikestd::views::transform’s, they do not point into the range object and outlive it, so it is astd::ranges::borrowed_range(a function may return it, Python may iterate it after the call). It traverses asrangedoes (random access, sized where it is), ends atrange’s sentinel where it has one, and hasbase(),function()and a checkedat(position).rangemust outlive it unless it is a borrowed view (iota, a span, the set bits of a bitset); a view that is not borrowed is refused, andtransformtakes an rvalue only where it is borrowed. The function is a small copy-constructible function object: a lambda (capturing by value what it reads through, such as a view) or a struct holding it (a pointer). It is held in a box, asstd::views::transformholds it, so that the range and its iterators stay default-constructible and assignable; each iterator holds its own copy. It owns nothing else. Where what it reads is a value range (urd::value_range) or a view, it is a value range itself.
-
template<typename M, typename C>
using resolved_range = transformed_range<M, resolver<C>>¶ A range of resolving elements read through a context: the
transformed_rangeofMwhose function,resolver<C>({ &context }), resolves each element (resolve()) through the context, which it borrows; whatresolutionmakes of a range of indices.context()gives the context. It can be spelled whileCis still incomplete.
-
template<typename T>
struct context_of¶ Names a record type’s default context once (
template<> struct urd::context_of<Atom> { using type = Repository; };), so thatindex_view<Atom>needs no second argument.
-
template<typename T, typename V>
struct view_mixin¶ Where a library adds the named accessors of a record’s views, one line each over
member<>:template<typename V> struct urd::view_mixin<Atom, V> { auto predicate() const { return static_cast<const V&>(*this).template member<&urd::record_of<V>::predicate>(); } };, whererecord_of<V>is the record the view reads (named throughV, so it may be incomplete where the mixin is declared). The mixin is keyed on what the view reads: views of an index and of a record ofTshareT’s mixin; views of a builder usebuilder<T>’s, which isT’s unless specialized. The view’smember<&T::m>()hides anymembera mixin would declare.
Children of a record¶
urd/elements/children.hpp walks the handles a record holds: its children, every index<U> with U in a
type list Types, found in its cista members directly, in (nested) flat vectors, as a variant’s alternative, in an
optional or a pair, or in a nested record. U may be the record’s own type T: only a direct member of type
index<T>, the record’s own handle, is no child. An index of a type not in Types is neither handed out nor
walked into.
-
template<typename Types, typename T, typename R, typename C, typename F>
void for_each_child(const R &record, const C &context, F f)¶ Calls
f(context.view(child))for each child, in member order.
-
template<typename Types, typename T, typename R, typename C, typename F>
R map_children(R record, const C &context, F f)¶ The record with each child
creplaced byto_stored(f(context.view(c))):freturns an index ofc’s type or a view of one. A variant keeps its alternative.
-
template<typename Types, typename T, typename R, typename C, typename F>
R map_variant_children(R record, const C &context, F f)¶ map_children, but eachflat_variantslot not inside another variant (whatever its alternatives) is replaced whole, byto_stored(f(resolve(slot, context))):fgets thestd::variantof the slot’s alternatives resolved and may return another alternative (anotbecoming anor), as a view, an index or the variant. The walk does not enter a slot: the children inside it aref’s to map. Children outside variants are replaced asmap_childrenreplaces them, softakes both views and resolved variants (an overload set, or a generic callback).