Reputation: 2148
Is it possible to get a variant's member called x
even if it has a different type in each alternative?
#include <variant>
class A {
int x = 0;
};
class B {
int x = 1;
};
class C {
bool x = false;
};
std::variant<A, B, C> variant = A{};
template<typename R> R getValue(std::variant<A, B, C>& variant ) {
//return the value of x here
}
On Walk over variants that share the same member and get it?, I asked the same question, but it didn't specify that I wanted to get different types of values. This answer talks about std::visit()
but that does not accept a lambda that returns different types.
Upvotes: 2
Views: 573
Reputation: 6131
One more approach, again using visit. :) One thing to realize is that visit
doesn't just take a single visitor, but can take a full overload set of visitor functions, allowing different functions to handle the different values.
Thus you can flip the problem around and instead of trying to return one of the different values from your variant, you can provide handlers for the values and send the value to the proper handler.
For example, using the overloaded function-call operator in a struct:
struct A { int x = 0; };
struct B { std::string x = "2"; };
struct C { bool x = true; };
int main() {
struct Process {
void operator()(A const& a) const {
cout << "Got an A: " << a.x << '\n';
}
void operator()(B const& b) const {
cout << "Got a B: " << b.x << '\n';
}
void operator()(C const& c) const {
cout << "Got a C: " << c.x << '\n';
}
};
var = A{};
std::visit(Process{}, var);
var = B{};
std::visit(Process{}, var);
var = C{};
std::visit(Process{}, var);
}
A "modern" popular alternate syntax utilizing a bunch of c++17 features (including CTAD, variadic templates, deduction guides, inheriting from lambdas, and variadic using declarations in just a few lines) also looks nice, and is approximately equivalent to the above, though the first 2 lines defining the overloaded
class could be considered library code even if they do not make full sense yet.
// probably library code
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
struct A { int x = 0; };
struct B { std::string x = "2"; };
struct C { bool x = true; };
int main() {
// and then we have this nice, concise syntax:
auto process = [](auto & variant) {
std::visit(
overloaded{
[](A const& a) { cout << "Got an A: " << a.x << '\n'; },
[](B const& b) { cout << "Got a B: " << b.x << '\n'; },
[](C const& c) { cout << "Got a C: " << c.x << '\n'; }
},
var);
};
var = A{};
process(var);
var = B{};
process(var);
var = C{};
process(var);
}
Here's both versions together in Compiler Explorer: https://godbolt.org/z/8T91W4T9G
Upvotes: 2
Reputation: 13750
This is what you look for, I believe:
template<typename... Ts>
auto getValue(std::variant<Ts...>& v )
{
using R = std::common_type_t<decltype(Ts::x)...>;
return std::visit([](auto &obj){return static_cast<R>(obj.x);}, v);
}
Above code doesn't require you to state the type you want back but rather deduces it by using std::common_type
of the variable x
in all the different possible values in your variant
.
Code: http://coliru.stacked-crooked.com/a/a1f54902ea1a9db3
Upvotes: 2
Reputation: 6637
With the signature and template of your getValue
function, you would actually need to manually decide the type of R
when calling it. So in main, you would call it like:
std::cout << getValue<int>(variant); // prints 0;
And if that is what you intended, then you can simply cast the .x
to type R
:
template<typename R>
R getValue(std::variant<A, B, C>& v )
{
return std::visit([](auto &obj){return R(obj.x);}, v);
}
However, I highly doubt that's what you intended.
Upvotes: 1