I have been struggling a bit to get my code using std::variant
in combination with std::visit
to work. I have now reduced my code to a minimum of just using one type in my variant and still the compiler complains about no matching call to visit.
#include <iostream>
#include <variant>
#include <vector>
#include <array>
class SomeClass {
public:
std::vector<int> foo(const std::string& s, const std::array<float, 3>& d){
return {};
}
};
struct Visitor{
public:
std::vector<int> operator() (SomeClass& c1, const std::string& s, const std::array<float, 3>& a) {
return c1.foo(s, a);
}
};
int main() {
std::variant<SomeClass> variants;
std::string s = "c ";
std::array<float, 3> a {1, 0, 0};
std::visit(Visitor{}, variants, s, a);
return 0;
}
In am using gcc 11.3
CodePudding user response:
std::visit
expects every argument after the visitor to be a variant
. It will "unwrap" every variant to the underlying object and then make a call to the visitor with all of those objects. It will give an error if you try to give it non-variant
s like string
s.
A visitor can have its own internal state/variables. You want to bring s
and a
into your visitor. That is, s
and a
are not conceptually arguments to the visitor. You should think of them as part of the visitor, since the visitor object represents "what to do to the variant
" and s
and a
are parts of that. s
and a
are not inputs to the visitor because the input to the visitor should be the variant.
struct Visitor {
std::string s;
std::array<float, 3> a;
std::vector<int> operator()(SomeClass &c) {
return c.foo(s, a);
}
};
std::variant<SomeClass> variants;
Visitor v{"c ", {1, 0, 0}};
std::visit(v, variants);
// you *could* shorten this to a lambda with captures
// std::string s = "c "
// std::array<float, 3> a{1, 0, 0};
// std::visit([&](SomeClass &c) { return c.foo(s, a); }, variants);
// but this will not generalize to multiple things in the variant (lambdas don't overload)
There is also the following hack. I would consider it bad style (since it doesn't correctly separate the visitor from what's being visited), but if you must use it works.
template<typename T>
std::variant<std::reference_wrapper<T>> unvisit(T &x) {
return std::ref(x);
}
// using your original Visitor this time
std::variant<SomeClass> variants;
std::string s = "c ";
std::array<float, 3> a {1, 0, 0};
std::visit(Visitor{}, variants, unvisit(s), unvisit(a));
I.e. you may wrap every "non-variant
" argument into a single-variant variant
by reference and then rely on visit
to unwrap it again.
CodePudding user response:
std::visit
calls the visitor by always passing a single argument to the visitor: the object the visitor is visiting.
Additional parameters to std::visit
are additional variants to visit.
std::visit(Visitor{}, variants, s, a);
This means: use the visitor{}
to visit variants
, then visit s
, then finally a
. All of these are expected to be, themselves, variants.
In other words: std::visit
does not work the way you thought it worked. You'll need to come up with some other, alternative, means to visit your variants.