Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::visit take a variable number of variants?

Trying to get more familiar with C++17, I've just noticed std::visit:

template <class Visitor, class... Variants>
constexpr /*something*/ visit(Visitor&& vis, Variants&&... vars);

Why does std::visit not take a single variant, but rather any number of variants? I mean, you can always take some standard library function and have it take multiple parameters with the same role, working on all of them (e.g. std::find() for multiple elements in a container); or you could be taking multiple visitors and using them on the same variant.

So, why this specific 'variadification'?

like image 515
einpoklum Avatar asked May 05 '17 18:05

einpoklum


People also ask

What is the point of std :: variant?

The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).

How does C++ variant work?

It holds one of several alternatives in a type-safe way. No extra memory allocation is needed. The variant needs the size of the max of the sizes of the alternatives, plus some little extra space for knowing the currently active value. By default, it initializes with the default value of the first alternative.

Does STD variant use RTTI?

Since this function is specific to a given type, you don't need RTTI to perform the operations required by std::any .


2 Answers

To make multiple visitation cleaner. Let's say I had two std::variant<A,B>, one named left and one named right. With multiple visitation, I can write:

struct Visitor {
    void operator()(A, A);
    void operator()(A, B);
    void operator()(B, A);
    void operator()(B, B);
};

std::visit(Visitor{}, left, right);

That's a pretty clean interface, and is something that's pretty commonly useful. It's also easy to implement efficiently - you just create a n-dimensional array of functions instead of a one dimensional array.

On the other hand, with only single visitation, you'd have to write:

std::visit([&](auto l_elem){
    std::visit([&](auto r_elem){
        Visitor{}(l_elem, r_elem);
    }, right)
}, left);

That's miserable to write, miserable to read, and likely less efficient too.

like image 176
Barry Avatar answered Oct 05 '22 06:10

Barry


Because we need to allow for visitation of combinations of classes within variants. That is, if we have

using Var1 = std::variant<A,B>;
using Var2 = std::variant<C,D>;

we can obviously use these kinds of visitors:

struct Visitor1 {
    void operator()(A);
    void operator()(B);
};

struct Visitor2 {
    void operator()(C);
    void operator()(D);
};

with Var1 and Var2 respectively. We can even use this next kind, with both Var1 and Var2 individually:

struct Visitor3 {
    void operator()(A);
    void operator()(B);
    void operator()(C);
    void operator()(D);
};

but what OP is missing is that we want to be able to visit one of the four pairs (A,C), (A,D), (B,C), (B,D) - when looking at a pair of Var1 and Var2 together. That's why the variadic argument to std::visit is all-but-necessary. An appropriate visitor would look like this:

struct Visitor4 {
    void operator()(A,C);
    void operator()(A,D);
    void operator()(B,C);
    void operator()(B,D);
};

and we would call std::visit(Visitor4{}, my_var1_instance, my_var2_instance);

I figured this out when reading Barry's answer.

like image 20
einpoklum Avatar answered Oct 05 '22 06:10

einpoklum