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:
destruction,
the invocation of
is_invalid()
andis_valid()
,copy/move assignment from, and swapping with, a valid
wrap
,generic assignment.
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:
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);