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'?
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).
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.
Since this function is specific to a given type, you don't need RTTI to perform the operations required by std::any .
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With