Dynamically Types Boxes#
- <amongoc/box.h> (header file)#
- <amongoc/box.hpp> (header file)#
This header defines types and functions for dealing with
amongoc_box, a generic container of arbitrary values.
Types#
-
struct [[zero_initializable]] amongoc_box#
A type-erased boxed value of arbitrary type. The box manages storage for the boxed value and may contain a destructor function that will be executed when the box is destroyed.
- Zero-initialized:
[[zero_initializable]]A zero-initializedamongoc_boxis equivalent toamongoc_nil.- Header:
When a box parameter or return value is documented with
[[type(…)]], this specifies the type that is expected to be contained within the box. The special type[[type(nil)]]refers to a theamongoc_nilspecial box value.In C code, use
amongoc_box_init()to initialize box objects.When working with C++ code, prefer to use
amongoc::unique_box, as it will prevent accidental copying and ensures the destructor is executed on the box when it goes out of scope.Members
All
amongoc_boxdata members other thanvieware private and should not be accessed or modified.-
amongoc_view view#
Accessing this member will yield an
amongoc_viewthat refers to this box’s contents.Note
It is only legal to access this member if the box is active
-
as_unique() &&#
(C++ API) Move the box into an
amongoc::unique_boxto be managed automatically.
-
struct amongoc_view#
A read-only view of an
amongoc_boxvalue.- Header:
An
amongoc_view\(V\) is valid if it was created by accessing theamongoc_box::viewmember of an activeamongoc_box\(B\), and is only valid as long the original \(B\) remains active. Consuming a box \(B\) will invalidate allamongoc_viewobjects that were created from \(B\).-
template<typename T>
T &as() noexcept# The same as
amongoc::unique_box::as()
-
type __box_or_view#
A special exposition-only parameter type representing either an
amongoc_boxor anamongoc_view.
-
using amongoc_box_destructor = void (*)(void *p)#
Type of the destructor function that may be associated with a box. The function parameter
pis a pointer to the object that was stored within the box.After the destructor function is invoked, any dynamic storage associated with the box will be released.
-
using amongoc::box = ::amongoc_box#
amongoc::boxis a type alias of::amongoc_box- Header:
-
class amongoc::unique_box#
(C++ API) Wraps an
amongoc_box, restricting copying and ensuring destruction to prevent programmer error. Theunique_boxis move-only.- Header:
Note
unique_boxis not default-constructible. If you want a reasonable “nothing” box, usingamongoc::nil()to initialize a new instance.-
unique_box(amongoc_box&&)#
Take ownership of the given box. The box must be passed as an rvalue-reference to emphasize this ownership transfer. The moved-from box will be overwritten with
amongoc_nil.
-
~unique_box()#
Destroy the underlying box.
-
operator amongoc_view()#
Implicit conversion to an
amongoc_view
-
template<typename T>
T &as() noexcept# Obtain an lvalue reference to the contained value of type
T.- Precondition:
The box must be active for the type
T.- C API:
-
template<typename T>
static unique_box from(mlib::allocator<>, T &&value)# Construct a new
unique_boxby decay-copying from the given value. This should be the preferred way to create box objects within C++ code.- Throws:
std::bad_alloc – If memory allocation fails. This will never throw if the box is small.
- Postcondition:
The returned box object is active for the decayed type of
T.
-
template<typename T, typename D>
static unique_box from(
)# Create a new box object by copying the given value and imbuing it with a destructor based on
D. The typeTmust be trivially destructible, because the box will instead useDas a destructor.In general, the given destructor should be a stateless function-object type (e.g. a lambda expression with no captures) that accepts a
T&and destroys the object. Using anything else (e.g. a function pointer) will not work.- Throws:
std::bad_alloc – If memory allocation fails. This will never throw if the box is small.
- Postcondition:
requires std::is_trivially_destructible_v<T>
-
template<typename T, typename ...Args>
static unique_box make(
)# In-place construct a new instance of
Tinto a new box.- Parameters:
a – The allocator to be used for the box.
args – Constructor arguments for the new
T.
-
[[nodiscard]] amongoc_box release() && noexcept#
Relinquish ownership of the underlying box and return it to the caller. This function is used to interface with C APIs that will
[[transfer]]anamongoc_boxby-value.
Functions & Macros#
Box Creation / Destruction#
-
amongoc_box_init(amongoc_box b, __type T)#
-
amongoc_box_init(amongoc_box b, __type T, amongoc_box_destructor dtor)#
- amongoc_box_init(
- amongoc_box b,
- __type T,
- amongoc_box_destructor dtor,
- mlib_allocator alloc,
-
amongoc_box_init_noinline(amongoc_box b, __type T)#
-
amongoc_box_init_noinline(amongoc_box b, __type T, amongoc_box_destructor dtor)#
- amongoc_box_init_noinline(
- amongoc_box b,
- __type T,
- amongoc_box_destructor dtor,
- mlib_allocator alloc,
Initialize a box to contain a zero-initialized storage for an instance of type
T.- C++ API:
- Parameters:
b – An modifiable lvalue expression of type
amongoc_box. This is the box that will be initiatlized.T – The type that should be stored within the box.
dtor – A destructor function that should be executed when the box is destroyed with
amongoc_box_destroy(). The destructor function should be convertible to a function pointer:amongoc_box_destructor. If omitted, the box will have no associated destructor.alloc – An
mlib_allocatorobject to be used if the box requires dynamic allocation. If omitted, the default allocator will be used.
- Returns:
Returns a non-
constpointer toT. If memory allocation was required and fails, this returnsnullptr. Note that a small type used withamongoc_box_init()will not allocate, so the returned pointer in such a scenario will never be null.
The
_noinlinevariant will inhibit the small-object optimization, which is required if the object being stored is not relocatable (i.e. it must be address-stable).
-
void amongoc_box_destroy(amongoc_box [[transfer]] b)#
Consume the given box and destroy its contents.
- Parameters:
b –
[[transfer]]The box that will be consumed and whose contained value will be destroyed.
-
void amongoc_box_free_storage(amongoc_box [[transfer]] b)#
Note
Do not confuse this with
amongoc_box_destroy()This function will release dynamically allocated storage associated with the given box without destroying the value that it may have contained.
This function should be used when the value within the box is moved-from, and the box itself is no longer needed.
Inspection#
-
T amongoc_box_cast(__type T, __box_or_view box)#
- Parameters:
T – The target type for the cast expression.
- C++ API:
Perform a cast from an
amongoc_boxoramongoc_viewto an lvalue expression of typeT.Note that because the result is an lvalue expression, this cast expression can be used to manipulate the value stored in the box:
void changed_boxed_int(amongoc_box* b) { // Replace the boxed integer value with 42 amongoc_box_cast(int, *b) = 42; }
If the given box is not active for the type
T, then the behavior is undefined.
-
void *amongoc_box_data(amongoc_box b)#
-
const void *amongoc_box_data(const amongoc_box b)#
-
const void *amongoc_box_data(amongoc_view b)#
Obtain a pointer to the object stored within a box
b. Expands to an r-value of typevoid*. Ifbis aconstbox or anamongoc_view, the returned pointer is a pointer-to-const.- C++ API:
Note
This is implemented as a C preprocessor macro.
-
void amongoc_box_take(auto dest, amongoc_box [[transfer]] box)#
Moves the value stored in
boxto overwrite the objectdest.- Parameters:
dest – A non-
constlvalue expression of type \(T\) that will receive the boxed value.box –
[[transfer]]A non-constbox that is active for the type \(T\).
This is useful to move an object from the type-erased box into a typed storage variable for more convenient access. The dynamic storage for
boxwill be released, but the destructor for the box will not be executed. The object is now stored withindestand it is up to the caller to manage its lifetime.Note
This is implemented as a C preprocessor macro.
Example
struct my_large_object { int values[64]; }; // ... void foo(amongoc_box large) { my_large_object o; amongoc_box_take(o, large); // `o` now has the value from `large`, and dynamic storage for `large` // has been released. }
Trivial Box Constructors#
-
amongoc_box amongoc_box_pointer(const void *x)#
-
amongoc_box amongoc_box_float(float x)#
-
amongoc_box amongoc_box_double(double x)#
-
amongoc_box amongoc_box_char(char x)#
-
amongoc_box amongoc_box_short(short x)#
-
amongoc_box amongoc_box_int(int x)#
-
amongoc_box amongoc_box_unsigned(unsigned int x)#
-
amongoc_box amongoc_box_long(long x)#
-
amongoc_box amongoc_box_ulong(unsigned long x)#
-
amongoc_box amongoc_box_longlong(long long x)#
-
amongoc_box amongoc_box_ulonglong(unsigned long long x)#
-
amongoc_box amongoc_box_size(size_t x)#
-
amongoc_box amongoc_box_ptrdiff(ptrdiff_t x)#
-
amongoc_box amongoc_box_int8(int8_t x)#
-
amongoc_box amongoc_box_uint8(uint8_t x)#
-
amongoc_box amongoc_box_int16(int16_t x)#
-
amongoc_box amongoc_box_uint16(uint16_t x)#
-
amongoc_box amongoc_box_int32(int32_t x)#
-
amongoc_box amongoc_box_uint32(uint32_t x)#
-
amongoc_box amongoc_box_int64(int64_t x)#
-
amongoc_box amongoc_box_uint64(uint64_t x)#
Convenience functions that initialize a new
amongoc_boxwith the type and value of the given argument.Note that all of the boxes returned by these functions are trivial.
-
unique_box amongoc::nil() noexcept#
Returns a unique box containing no value.
- C API:
Constants#
-
const amongoc_box amongoc_nil#
A box value that contains no value. The resulting
amongoc_boxis trivial. Destroying a box constructed fromamongoc_nilis a no-op.- C++ API:
Note
This is implemented as a C preprocessor macro.
-
template<typename T>
constexpr bool amongoc::enable_trivially_relocatable_v# Trait variable template that determines whether
amongoc::unique_box::from()will try to store an object inline within a box (omitting allocation).- Header:
amongoc/relocation.hpp
By default any objects that are both trivially destructible and trivially move-constructible are considered to be trivially relocatable.
By the above rule all of the following are considered trivially relocatable:
All built-in language types
All pointer types
All class types that have no non-trivial move/destroy operations (including all pure C structs)
C++ closure objects that have no non-trivial move/destroy operations (this is based on the type of values that it captures).
Additionally, if the type
Thas a nested typeenable_trivially_relocatablethat is defined toT, then the object will be treated as trivially relocatable.
Box Behavior#
At any given time, an amongoc_box is either active for type T, or dead.
State: Active for type \(T\)#
A box \(B\) is active for type \(T\) if either:
\(B\) was used with
amongoc_box_init()/amongoc_box_init_noinline()with the type \(T\)OR \(B\) was created with a C++ API that constructs a box,
OR \(B\) is a by-value copy of an
amongoc_boxthat was already active for type \(T\).
AND:
\(B\) has not been consumed by any operation (i.e. passed through a
[[transfer]]parameter)
If a box is active for type \(T\), then it is legal to use it in
amongoc_box_cast() with type \(T\).
State: Dead#
A box \(B\) is dead if either:
\(B\) is newly declared and uninitialized.
or \(B\) was used in any operation that consumed it.
Consuming Operations#
A box \(B\) is consumed by any of the following operations:
Passing \(B\) by-value to any function parameter marked with
[[transfer]].Returning \(B\) by-value from a function.
Copy-assigning \(B\) into another l-value expression of type
amongoc_box.
Relocation#
The amongoc_box should be considered trivially relocatable. That is: A
byte-wise copy of the object can be considered a moved-to amongoc_box,
invalidating the box that was copied-from (i.e.
consuming it).
Smallness#
amongoc_box considers some objects to be “small”. If those objects are small,
then it is guaranteed that amongoc_box will not allocate memory for storing
those objects.
The only types guaranteed to be considered “small” are objects no larger than two pointers.
Non-Relocatable Types#
To store an object that cannot be trivially relocated within an amongoc_box,
one should use amongoc_box_init_noinline(), which forcibly disables the
small-object optimization within the created box.
The C++ APIs amongoc::unique_box::from() will automatically handle this
distiction by consulting amongoc::enable_trivially_relocatable_v.
Triviallity#
An amongoc_box is said to be trivial if the type it contains is
small and the box has no associated destructor.
When a box is trivial, some usage requirements relax:
A trivial box may be copied arbitrarily without invalidating other copies, and each copy has a distinct identity.
It is safe to discard a trivial box (allow it to leave scope) without ever calling
amongoc_box_destroy().It is safe to overwrite or reinitialize the box (e.g.
amongoc_box_init()) with a new value without first destroying the box.
In general: the semantics of the [[transfer]] attribute do not apply to
trivial boxes.
Note
It is not sufficient that the box is simply small or contains a primitive
type: It is possible that such a box has a destructor that needs to execute on
the primitive’s value (e.g. POSIX close is a destructor for an int).
Storage Alignment#
Important
At the current time, boxes allocate and store values using the default maximum-alignment defined by the compiler. There is not yet support for types that require additional alignment.
Q: “Can I query the state of a box?”#
In general, no. The properties of a box (i.e. type, state, triviallity, smallness, whether it is nil, and whether it has a destructor) are stored as implementation details. Code should be designed to treat all live boxes as non-trivial unless they are known to be otherwise.
Attributes of boxes may be carried in other channels, e.g through an associated
amongoc_status parameter, but it is up to the particular box+status pair to
define the semantics thereof.