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

2

22

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'?

Catholicism answered 5/5, 2017 at 18:37 Comment(0)
C
3

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.

Catholicism answered 5/5, 2017 at 19:18 Comment(0)
B
29

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.

Battles answered 5/5, 2017 at 19:4 Comment(3)
So, I figured out the reason for needing std::visit to be the way it is by reading your answer, but you're kind of assuming I know the answer already. See my suggested answer below...Catholicism
@Catholicism You figured out the answer from reading my answer, but my answer didn't help you figure out the answer?Battles
It did (which is why I +1 you), but it only helped me figure out the answer, it was not the answer.Catholicism
C
3

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.

Catholicism answered 5/5, 2017 at 19:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.