Reputation: 132350
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'?
Upvotes: 22
Views: 3290
Reputation: 304182
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.
Upvotes: 29
Reputation: 132350
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.
Upvotes: 3