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

einpoklum
  • 118,144
  • 57
  • 340
  • 684

2 Answers2

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 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... – einpoklum May 05 '17 at 19:19
  • 9
    @einpoklum You figured out the answer from reading my answer, but my answer didn't help you figure out the answer? – Barry May 05 '17 at 19:22
  • It did (which is why I +1 you), but it only helped me figure out the answer, it was not _the_ answer. – einpoklum May 05 '17 at 19:23
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.

Community
  • 1
  • 1
einpoklum
  • 118,144
  • 57
  • 340
  • 684