More on construction#

Default construction#

By default, the default constructor of wrap is disabled. There are two ways of enabling default construction for a wrap.

First, if the config::invalid_default_ctor option is activated, then default construction will initialise a wrap into the invalid state. This can be useful to model types such as std::function for which an empty state is meaningful.

The other option is to specify a custom (i.e., non-void) DefaultValueType as first template argument in config, in which case the default constructor of wrap will value-initialise an interal value of type DefaultValueType.

If both the config::invalid_default_ctor option is activated and a custom DefaultValueType is specified, then the config::invalid_default_ctor option takes the precedence.

The invalid state#

A wrap object is in the invalid state when it is empty, that is, it does not contain any value. The validity of a wrap can be checked via the is_invalid() and is_valid() functions.

A wrap object can become invalid in a variety of circumstances:

  • it is default-constructed when the config::invalid_default_ctor option is activated,

  • it is created (or assigned) from an instance of invalid_wrap_t,

  • it has been moved-from,

  • it has been swapped with an invalid wrap,

  • the generic assignment operator threw an exception,

  • emplacement into an existing wrap threw an exception,

  • deserialisation threw an exception.

The only allowed operations on an invalid wrap are:

Note that the invalid status is only about whether or not a wrap contains a value – a valid wrap could be containing a moved-from value, and it is up to the user to handle such an occurrence.

Customising wrap’s constructors#

Beside the default constructor, it is also possible to customise the behaviour of wrap’s other constructors in a variety of ways.

wrap’s generic constructors, for instance, are by default explicit, but they can be made implicit by altering the config::explicit_ctor configuration setting. If config::explicit_ctor is set to wrap_ctor::ref_implicit, then generic construction is implicit when wrapping a reference, explicit otherwise. If config::explicit_ctor is set to wrap_ctor::always_implicit, then generic construction is always implicit.

By default, wrap is copy/move constructible and copy/move assignable. The copy/move constructors and assignment operators can be disabled by switching off the config::copyable and config::movable configuration settings. The wrap is also by default swappable. Swappability can be switched on/off via the config::swappable configuration setting.

Copyability, movability and swappability are subject to several sanity checks and constraints. Specifically:

  • a copyable wrap must also be movable, and a movable wrap must also be swappable;

  • when employing value semantics (the default), it is not possible to create a copyable/movable/swappable wrap containing a value type which is not also copyable/movable/swappable.

Emplacement#

In the examples we have seen so far, a wrap was always constructed by copying/moving in a value. Sometimes, however, it is necessary to type-erase values which are neither copyable nor movable. A classic example is std::mutex. In order to be able to wrap such types, wrap supports emplacement, that is, a form of direct initialisation that does not require to pass through a temporary object.

A wrap can be emplace-constructed via a special constructor that, similarly to std::variant, takes in input an instance of std::in_place_type_t (representing the desired value type) and a variadic pack. The variadic pack is used to directly construct an object of the desired type.

Let us see a short example with std::mutex. We have our usual minimal interface and its implementation:

template <typename Base, typename Holder, typename T>
struct any_iface_impl : public Base {
};

struct any_iface {
    template <typename Base, typename Holder, typename T>
    using impl = any_iface_impl<Base, Holder, T>;
};

We can emplace-construct a wrap containing a std::mutex:

    using any_wrap = tanuki::wrap<any_iface, tanuki::config<>{.copyable = false, .movable = false, .swappable = false}>;

    // Emplace-construct with std::mutex.
    any_wrap w(std::in_place_type<std::mutex>);

Note that in this specific case the variadic pack is empty (as the constructor of std::mutex takes no arguments) and thus only the std::in_place_type_t argument is required. Note also that we used a custom configuration in which we disabled copy/move/swap operations: this is necessary because std::mutex cannot be copied/moved/swapped, and thus, as explained in the previous section, the wrap’s copy/move/swap primitives must also be disabled.

In addition to the emplace constructor, the emplace() function is also available to emplace-assign a value into an existing wrap object. Here is a simple example:

    // Reset to the invalid state.
    w = tanuki::invalid_wrap;

    // Emplace into existing wrap.
    emplace<std::mutex>(w);