Any++
Loading...
Searching...
No Matches
Terms and Definitions in Context of Any++

Trait jacket

A trait is a collection of related functions that an object must implement to conform to that trait. A trait is defined using the TRAIT macro. A trait is user visible via its trait jacket. This is a template struct generated by the TRAIT macro. It presents the interface of the trait to the user. The generated functions in the trait jacket forward calls to the underlying object's functions via the associated v_table, or via static dispatch if the unerased type is known at compile time, to the corresponding member function in the model map.

Model

A model is a concrete type that implements the functions defined in one ore more traits.

Model map

A model map is a template struct generated by the TRAIT macro. It maps the functions of the trait jacket to the corresponding member functions of the unerased type (Model). By default, the model map assumes that the unerased type has member functions with the same name and signature as the functions in the trait jacket. This behavior can be customized by specializing the model map for specific types. Additionaly the TRAIT macro generates a specialized version for std::variant types.

v_table

An object composed of function pointers, where each function expects self as its first argument. This is a pointer to (eventually const) void. The v_table points to a meta_data object and optionally to an m_table.

v_table instance

An object derived from v_table, with no additional members, and where all members point to valid functions.

These functions are implemented via a templated constructor. The template parameter is called the unerased type.
In these functions, the self parameter is a cast back to a pointer to the unerased type, and the correct function for the unerased type will be called. By default, the called function is a member function with the same name and signature as the v_table function. This behavior can be customized in a model_map for the unerased type. Tutorial

open dispatch, dispatch_table, dispatch_table instance, multi dispatch matrix

When an any is enabled for open dispatch (see open multi-methods), its v_table instance holds a pointer to a dispatch table. An open dispatch singleton object holds an index for each involved v_table. If the dispatch is only on one v_table, the dispatch table holds direct pointers to the target functions at that index. If the dispatch is on multiple v_table**s, the dispatch table holds the dispatch index in the **multi dispatch matrix. In this case the target function for the multi dispatch resides in the multi dispatch matrix.

Any++'s open dispatch class is a recipe to solve the expression problem.

Proxy

A concept describing an object that manages access to the object associated with the any.

A proxy can be a concrete object or a type-erased handle to an object.

The interplay with any is controlled via a specialization of proxy_trait.

The library offers these lifetime holders:

  • ref: Takes no ownership. The creator of such an observer is responsible for ensuring that the referenced object outlives the observer. There are two flavors: const and mutable, for read-only or modifying access to the referenced object.
  • shared: Ownership as std::shared_ptr. The delivered address is a pointer to const void.
  • weak: Ownership as std::weak_ptr. No delivered address. Use lock to gain a shared pointer, if the object still exists.
  • unique: Ownership equivalent to std::unique_ptr. The delivered address is a pointer to a mutable object.
  • val: Every value object holds its own copy. Same semantics as int. The delivered void pointer is mutable. The storage has Small Object Optimization (SOO).

'any' Versus 'typed_any'

An any combines a v_table with a proxy.

A typed_any is a convenience wrapper of an any for the unerased type.

Static Cast vs Dynamic Cast

A static cast:

  • Is enforced by the language type rules and always checked by the compiler.
  • Upcasts are always safe and static.
  • Static downcasts are unsafe.
  • Static casts are only a syntactic construct and leave no trace in the binary code.

A dynamic cast:

  • Uses meta data to find the queried type.
  • Whether such a query succeeds is determined at runtime.
  • All dynamic casts are safe.
  • Dynamic casts have a runtime cost.

'Upcast' vs 'Downcast'

An upcast is a conversion from a more detailed type to a more general one.

A downcast is a conversion from a more general type to a more detailed one.

For upcasts and downcasts, the types must be related within the language rules.
Static downcasts are guesses and as such unsafe. Dynamic downcasts are by definition safe and a kind of type query. Anyxx supports dynamic downcasts between related types.

Lifetime Cast

A proxy can receive the content of another proxy with a different lifetime within certain rules. Three ways, three rules:

  • borrow: Assignment does not change ownership or constness.
  • move: Left hand can hold ownership, right hand has ownership, left hand is const or right hand is mutable.
  • clone: Left hand can hold ownership.

Crosscast

While upcasts and downcasts are within related types, crosscasts are between unrelated types.

A crosscast usually tests if one trait can be reached from another trait, and if so, provides access to it. To enable a crosscast to a certain trait for an object, the target trait's v_table instance must be registered for that object's class. This is done with the ANY_MODEL... macros.

Extension Members

An extension member behaves like a struct, where you can add data members without changing the definition of that struct.

This could be trivially implemented by a map from some kind of tag to an std::any.
This library offers an efficient implementation with index-based indirections and type-safe access.