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

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.

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:

  1. Use a recursive_wrapper.
    void
    goal() {
    variant<int, recursive_wrapper> v;
    v = 5;
    v = X();
    }
  2. 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();
    }
  3. Use emplace instead of assignment. This works even if X is not copyable or moveable.
    void
    goal() {
    variant<int, X> v;
    v.emplace(5);
    v.emplace();
    }