In some cases, you might want to use emplace
to initialize a variant to a specific type explicitly. For instance it might
be in an initializer list, and it's not an option to use the emplace
method.
The emplace constructor is selected using tag dispatch:
variant::variant(emplace_tag<T>, args...)
for example, in an initializer list, it looks like this:
my_struct::my_struct() : v(emplace_tag<std::string>{}, "foo") {}
A variant can be converted to a variant over strictly more types. This is
called the "generalizing" constructor. This was also supported
by boost::variant
.
variant<int, double> v; variant<int, double, std::string> v2{v};
This constructor also allows reordering the types, and adding or removing
recursive_wrapper
.
variant<int, double> v; variant<double, int> v2{v}; variant<recursive_wrapper<int>, double> v3; v3 = v; v2 = v3; v = v2;
One thing which you'll note in boost::variant
docs is that in boost::variant
, a visitor should always be a structure
and should derive from boost::static_visitor
,
or, failing that, explicitly define a typedef result_type
which matches the return type of the function object.
struct formatter : boost::static_visitor<std::string> { std::string operator()(const std::string & s) const { return s; } std::string operator()(int i) const { return "[" + std::to_string(i) + "]"; } };
In strict_variant
, this isn't
necessary, the return type will be deduced. So you can omit static_visitor
.
This is necessary to support lambdas being used as visitors, for instance.
You can also use a lambda as a visitor in some cases:
strict_variant::variant<int, float> v; v = 5; // Prints 10 std::cout << strict_variant::apply_visitor([](double d) -> double { return d * 2; }, v) << std::endl; v = 7.5f; // Prints 22.5 std::cout << strict_variant::apply_visitor([](double d) -> double { return d * 3; }, v) << std::endl;
One of the nicest things about visitors is that they can use templates -- when you have a visitor with many types which should be handled similarly, this can save you much typing. It also means that a single visitor can often be equally useful for many different variants, which helps you to be very DRY.
struct formatter { std::string operator()(const std::string & s) const { return s; } template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value>::type> std::string operator()(T t) const { return "[" + std::to_string(t) + "]"; } template <typename T> std::string operator()(const std::vector<T> & vec) const { std::string result = "{ "; for (unsigned int i = 0; i < vec.size(); ++i) { if (i) { result += ", "; } result += (*this)(vec[i]); } result += " }"; return result; } };
This generic visitor is appropriate for a wide variety of variants:
variant<int, double> v1; variant<float, std::string, std::vector<int>> v2; variant<long, std::vector<std::vector<std::string>>> v3; apply_visitor(formatter{}, v1); apply_visitor(formatter{}, v2); apply_visitor(formatter{}, v3);
A more elaborate use of the deduced return-type feature involves function objects that might return multiple different types depending on the arguments.
TODO
This works as long as the returns can be reconciled in a fashion similar
to std::common_type
. [12]
One feature of boost::variant
which we don't currently
support is delayed visitation. For this,
you will have to use a capturing lambda or some other sort of ad-hoc function
object.
apply_visitor( overload( [](double d) -> std::string { return std::to_string(d); }, [](std::string s) -> std::string { return s; } ), v1 );
strict_variant
has full support
for visiting 2 or more variants simultaneously, just as boost::variant
does.
However for this you must include <strict_variant/multivisit.hpp>
.
Just like boost::variant
and any other standard container,
strict_variant
can be moved,
copied and assigned.
However, like boost::variant
it has some restrictions.
nothrow-destructible
strict_variant
also requires
that its types are nothrow_destructible
.
It doesn't require that they are copyable or moveable.
However, strict_variant
is
different in that it is only assignable when each type is assignable AND
each type is nothrow_move_constructible
.
If this doesn't happen, compiling an assignment operation will fail with
a static_assert
.
Since you can't always make your types nothrow_move_constructible
,
there are a few handy solutions.
Suppose that our goal is code like this:
// A class with a throwing move class X { std::string foo; std::string bar; public: X() noexcept; X(const X &) noexcept(false); X & operator=(const X &) noexcept(false); }; // A variant containing X void goal() { variant<int, X> v; v = 5; v = X(); // Error: No assignment if X has a throwing move! }
This code will fail to compile, because v
cannot be assigned if X
has
a throwing move. We have a few possible remedies:
Use a recursive_wrapper
.
void goal() { variant<int, recursive_wrapper<X>> v; v = 5; v = X(); }
Use easy_variant
instead.
It's the same, but it implicitly applies wrappers to value types with
a throwing move.
void goal() { easy_variant<int, X> v; v = 5; v = X(); }
Use emplace
instead of
assignment. This works even if X
is not copyable or moveable.
void goal() { variant<int, X> v; v.emplace<int>(5); v.emplace<X>(); }
[12]
Note that we don't actually use std::common_type
:
for one, that is C++14, for two, it wouldn't allow us to return references.
We compromise by allowing the return of lvalue references but not rvalue
references -- non-lvalue references will decay when returned by apply visitor.