Advanced Usage (original) (raw)
Emplace constructor
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, args...)
for example, in an initializer list, it looks like this:
my_struct::my_struct() : v(emplace_tagstd::string{}, "foo") {}
Generalizing constructor
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 removingrecursive_wrapper.
variant<int, double> v; variant<double, int> v2{v};
variant<recursive_wrapper, double> v3; v3 = v; v2 = v3; v = v2;
More general visitors
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_visitorstd::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.
Lambda visitors
You can also use a lambda as a visitor in some cases:
strict_variant::variant<int, float> v; v = 5;
std::cout << strict_variant::apply_visitor([](double d) -> double { return d * 2; }, v) << std::endl;
v = 7.5f;
std::cout << strict_variant::apply_visitor([](double d) -> double { return d * 3; }, v) << std::endl;
Generic visitors
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::value>::type> std::string operator()(T t) const { return "[" + std::to_string(t) + "]"; }
template std::string operator()(const std::vector & 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> v2; variant<long, std::vector<std::vectorstd::string>> v3;
apply_visitor(formatter{}, v1); apply_visitor(formatter{}, v2); apply_visitor(formatter{}, v3);
Polymorphic return type
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.
Other interesting visitor styles
- In C++14, you can use generic lambdas as visitors also.
- If you incorporate code into your project like that proposed in PR0051, then you can glue multiple lambas together and use them as a visitor in one line:
apply_visitor( overload( [](double d) -> std::string { return std::to_string(d); }, [](std::string s) -> std::string { return s; } ), v1 );
Multivisitation
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>.
Throwing Assignment
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.
- Each type must be
nothrow-destructible - Each type must be copy or move-constructible
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:
class X { std::string foo; std::string bar;
public: X() noexcept; X(const X &) noexcept(false); X & operator=(const X &) noexcept(false); };
void
goal() {
variant<int, X> v;
v = 5;
v = X();
}
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> v;
v = 5;
v = X();
} - Use
easy_variantinstead. 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
emplaceinstead of assignment. This works even ifXis not copyable or moveable.
void
goal() {
variant<int, X> v;
v.emplace(5);
v.emplace();
}