Class Subspace :: sus :: boxed :: Box

template <class T>
class Box final
{ ... };

A heap allocated object.

A Box<T> holds ownership of an object of type T on the heap. When Box is destroyed, or an rvalue-qualified method is called, the inner heap object is freed.

Box is similar to std::unique_ptr with some differences, and should be preferred when those differences will benefit you:

  • Const correctness. A const Box treats the inner object as const and does not expose mutable access to it.
  • Never null. A Box always holds a value until it is moved-from. It is constructed from a value, not a pointer. Alternatively it can be constructed from the constructor arguments by with_args, like std::make_unique but built into the type. A moved-from Box may not be used except to be assigned to or destroyed. Using a moved-from Box will panic and terminate the program rather than operate on a null. This prevents Undefined Behaviour and memory bugs caused by dereferencing null or using null in unintended ways.
  • Supports up-casting (TODO: and down-casting) without leaving requiring a native pointer to be pulled out of the Box.
  • No native arrays, Box can hold Array or std::array but avoids API complexity for supporting pointers and native arrays (which decay to pointers) by rejecting native arrays.
  • Integration with concept type-erasure for holding and constructing from type-erased objects which satisfy a given concept in a type-safe way and without requiring inheritence. This construction is done through From and thus can Box<DynC> can be constructed in a type-deduced way from any object that satisfies the concept C via sus::into(). This only requires that DynC is compatible with DynConcept which is the case for all type-erasable concepts in the Subspace library.
  • Additional integration with Subspace library concepts like Error and Fn such that Box will satisfy those concepts itself when holding a type-erased object that satisfies those concepts.

Box implements some concepts for its inner type

The Subspace library provides a number of concepts which support type-erasure through DynConcept, and when Box is holding these as its value, it may itself implement the concept, forwarding use of the concept through to the inner type.

The canonical example of this is Result<T, Box<DynError>>, which allows construction via sus::err(sus::into(e)) for any e that satisfies Error. The error field, now being a Box is still usable as an Error allowing generic code that expects the Result to hold an Error to function even though the actual error has been type-erased and no longer needs the functions to be templated on it.

The following concepts, when type-erased in Box will also be satisfied by the Box itself, avoiding the need to unwrap the inner type and allowing the Box to be used in templated code requiring that concept:

Static Methods

template <class U>
Box(U u)
requires
std::convertible_to<U, T>
!::sus::ptr::SameOrSubclassOf<U*, T*>
sus::mem::Move<U>

Constructs a Box which allocates space on the heap and moves T into it.

template <class U>
Box(U u)
requires
sus::ptr::SameOrSubclassOf<U *, T *>
sus::mem::Move<U>

Constructs a Box which allocates space on the heap and moves T into it.

Box(Box<T>&& rhs)

Satisifes the Move concept for Box.

template <class U>
Box(Box<U>&& rhs)
requires
sus::ptr::SameOrSubclassOf<U *, T *>
template <class U>
static auto from(U u) -> Box<T>
requires
std::convertible_to<U, T>
!::sus::ptr::SameOrSubclassOf<U*, T*>
sus::mem::Move<U>

Converts U into a Box<T>.

The conversion allocates on the heap and moves u into it.

Satisfies the From<U> concept for Box<T> where U is convertible to T and is not a subclass of T.

template <class U>
static auto from(U u) -> Box<T>
requires
sus::ptr::SameOrSubclassOf<U *, T *>
sus::mem::Move<U>

Converts U into a Box<T>.

The conversion allocates on the heap and moves u into it.

Satisfies the From<U> concept for Box<T> where U is T or a subclass of T.

template <class U>
static auto from(U u) -> Box<T>
requires
sus::mem::Move<U>
std::same_as<U, std::remove_cvref_t<U>>
sus::boxed::DynConcept<T, U>
T::template SatisfiesConcept<U>

For a type-erased DynC of a concept C, Box<DynC> can be constructed from a type that satisfies C.

This satisfies the From<DynC, C> concept for constructing Box<DynC> from any type that satisfies the concept C. It allows returning a non-templated type satisfying a concept.

See DynConcept for more on type erasure of concept-satisfying types.

static auto from(std::string s) -> Box<T>
requires
std::same_as<T, ::sus::error::DynError>

Satisfies the From<std::string> concept for Box<DynError>. This conversion moves and type-erases the std::string into a heap-alloocated DynError.

static auto from_raw(UnsafeFnMarker, T* raw) -> Box<T>

After calling this function, the raw pointer is owned by the resulting Box. Specifically, the Box destructor will call the destructor of T and free the allocated memory. For this to be safe, the memory must have been allocated on the heap in the same way as the Box type, using operator new and must not be an array.

template <class... Args>
static auto with_args(Args&&... args) -> Box<T>
requires
std::constructible_from<T, Args &&...>

Constructs a Box by calling the constructor of T with args.

This allows construction of T directly on the heap, which is required for types which do not satisfy Move. This is a common case for virtual clases which require themselves to be heap allocated.

For type-erasure of concept objects using DynConcept, such as DynError, the from constructor method can be used to type erase the concept object and move it to the heap.

static auto with_default() -> Box<T>
requires
sus::construct::Default<T>

Constructs Box<T> with the default value for the type T.

Box can not satisfy Default because it prevents C++ from using Box to build recursive types where T holds an Option<Box<T>> as a member.

Methods

auto as_mut() & -> T&

Converts Box into a mutable reference of the inner type.

auto as_ref() const& -> const T&
auto as_ref() && -> const T&
deleted

Converts Box into a const reference of the inner type.

auto clone() const -> Box<T>
requires
sus::mem::Clone<T>

Returns a new box with a clone() of this box’s contents.

Satisfies the Clone concept if T satisfies Clone.

auto clone_from(const Box<T>& rhs) -> void
requires
sus::mem::Clone<T>

Copies source's contents into the contained T without creating a new allocation.

An optimization to reuse the existing storage for clone_into.

auto exact_size_hint() const -> sus::usize
requires
sus::iter::IteratorAny<T>
sus::iter::ExactSizeIterator<T, typename T::Item>

Implements ExactSizeIterator if T is an ExactSizeIterator, forwarding through to the inner T object.

auto into_inner() && -> T
requires
sus::mem::Move<T>

Consumes the Box, returning the wrapped value.

This allows the caller to make use of the wrapped object as an rvalue without leaving a moved-from T inside the Box.

auto into_raw() && -> T*

Consumes the Box, returning a wrapped raw pointer.

The pointer will be properly aligned and non-null.

After calling this function, the caller is responsible for the memory previously managed by the Box. In particular, the caller should properly destroy T and delete the memory, taking into account the alignment if any that would be passed by Box to operator new. The easiest way to do this is to convert the raw pointer back back into a Box with the Box::from_raw function, allowing the Box destructor to perform the cleanup.

Note: this is a not a static function, unlike the matching Rust function Box::into_raw, since in C++ the Box can not expose the methods of the inner type directly.

Examples

Converting the raw pointer back into a Box with Box::from_raw for automatic cleanup:

auto x = Box<std::string>("Hello");
auto* ptr = sus::move(x).into_raw();
x = Box<std::string>::from_raw(unsafe_fn, ptr);

Manual cleanup by explicitly running the destructor and deallocating the memory:

auto x = Box<std::string>("Hello");
auto* p = sus::move(x).into_raw();
delete p;
auto leak() && -> T&

Consumes and leaks the Box, returning a mutable reference, T&. Note that the type T must outlive the returned reference.

This function is mainly useful for data that lives for the remainder of the program's life. Dropping the returned reference will cause a memory leak. If this is not acceptable, the reference should first be wrapped with the Box::from_raw method producing a Box. This Box can then be dropped which will properly destroy T and release the allocated memory.

This method is not functionally different than into_raw but expresses a different intent, and returns a reference type indicating it can not ever return null.

Note: unlike with the Rust Box::leak this is not a static method, since Box can not expose the inner type's methods directly in C++.

auto next() -> auto
requires
sus::iter::IteratorAny<T>

Implements Iterator if T is an Iterator, forwarding through to the inner T object.

auto next_back() -> auto
requires
sus::iter::IteratorAny<T>
sus::iter::DoubleEndedIterator<T, typename T::Item>

Implements DoubleEndedIterator if T is a DoubleEndedIterator, forwarding through to the inner T object.

auto size_hint() const -> SizeHint
requires
sus::iter::IteratorAny<T>

Implements Iterator if T is an Iterator, forwarding through to the inner T object.

Operators

template <class... Args>
auto operator()(Args&&... args) const& -> sus::fn::Return<T, Args...>
requires
T::IsDynFn
sus::fn::Fn<T, sus::fn::Return<T, Args...> (Args...)>
template <class... Args>
auto operator()(Args&&... args) & -> sus::fn::ReturnMut<T, Args...>
requires
T::IsDynFn
sus::fn::FnMut<T, sus::fn::ReturnMut<T, Args...> (Args...)>
template <class... Args>
auto operator()(Args&&... args) && -> sus::fn::ReturnOnce<T, Args...>
requires
T::IsDynFn
sus::fn::FnOnce<T, sus::fn::ReturnOnce<T, Args...> (Args...)>

A Box holding a type-erased function type will satisfy the fn concepts and can be used as a function type. It will forward the call through to the inner type.

The usual compatibility rules apply, allowing DynFn to be treated like DynFnMut and both of them to be treated like DynFnOnce.

A Box<DynFnOnce> must be moved from when called, and will destroy the underlying function object after the call completes.

Examples

A Box<DynFn> being used as a function object:

const auto b = Box<sus::fn::DynFn<usize(std::string_view)>>::from(
    &std::string_view::size);
sus_check(b("hello world") == 11u);

auto mut_b = Box<sus::fn::DynFn<usize(std::string_view)>>::from(
    &std::string_view::size);
sus_check(mut_b("hello world") == 11u);

sus_check(sus::move(mut_b)("hello world") == 11u);
// The object inside `mut_b` is now destroyed.

A Box<DynFnMut> being used as a function object. It can not be called on a const Box as it requies the ability to mutate, and const Box treats its inner object as const:

auto mut_b = Box<sus::fn::DynFnMut<usize(std::string_view)>>::from(
    &std::string_view::size);
sus_check(mut_b("hello world") == 11u);

sus_check(sus::move(mut_b)("hello world") == 11u);
// The object inside `mut_b` is now destroyed.

A Box<DynFnOnce> being used as a function object. It must be an rvalue (either a return from a call or moved with move) to call through it:

auto b = Box<sus::fn::DynFnOnce<usize(std::string_view)>>::from(
    &std::string_view::size);
sus_check(sus::move(b)("hello world") == 11u);

auto x = [] {
  return Box<sus::fn::DynFnOnce<usize(std::string_view)>>::from(
      &std::string_view::size);
};
sus_check(x()("hello world") == 11u);
auto operator*() const -> const T&
auto operator*() -> T&
requires
!std::is_const_v<T>
auto operator->() const -> const T*
auto operator->() -> T*
requires
!std::is_const_v<T>
auto operator=(Box<T>&& rhs) -> Box<T>&

Satisifes the Move concept for Box.

template <class U>
auto operator=(Box<U>&& rhs) -> Box<T>&
requires
sus::ptr::SameOrSubclassOf<U *, T *>