Consider the following classes
class A {
public:
virtual std::string to_string() = 0;
};
class B : public A {
public:
std::string to_string(){
return "B";
}
};
class C : public A {
public:
std::string to_string(){
return "C";
}
};
class D {};
Now I will have a variant std::variant<B*, C*, D*> bcd;
The variable can of course, depending on the user input, hold either a variable of type B*
or of type C*
. At a given time in the program, I want to extract the value to pass to a method taking the superclass A*
as an argument. Now, if I do this explicitly like this:
bc = new C();
A* a = std::get<C*>(bc);
It works as expected. But I do not know at this point, which type the inner value of bc
will have.
I have tried the following:
Adding
A*
to the variant and trying to access it withstd::get<A*>(bc)
, which results in a bad variant access.Creating a visitor pattern to return the type (even though this seems cumbersome for this simple task) like this
class Visitor { template<typename TType> TType operator()(TType arg) { return arg; } }; A* a2 = std::visit(visitor, bc);
which produces a
no type named ‘type’ in ‘std::conditional_t‘
. (I also tried it without templates).
Is there a right / elegant way to do this without having to do something like
if(B* b = std::get_if<B*>(bc))
for every type that I have?
CodePudding user response:
You were close with std::visit
, not sure what the error is exactly but I would recommend using a lambda instead of a custom struct:
int main(){
std::variant<B*, C*> bc;
bc = new C();
A* a = std::visit([](auto&& e)->A*{return e;},bc);
a->to_string();
}
The above will compile iff all variants can be casted to A*
and is thus safe.
If some of variants are not derived from A*
, you could use this longer version:
A* a = std::visit(
[](auto& e) -> A* {
if constexpr (std::is_convertible_v<decltype(e),A*>)
return e;
else
return nullptr;
},
bc);
It will return nullptr
if the currently held type cannot be casted to A
.
One can hide the ugly lambda (the simple one above can be hidden too) into a global variable template, full example:
template <typename T>
constexpr auto shared_cast = [](auto& e) -> T* {
if constexpr (std::is_convertible_v<decltype(e), T*>)
return e;
else
return nullptr;
};
int main() {
std::variant<B*, C*, D*> bc;
bc = new C();
A* a = std::visit(shared_cast<A>, bc);
if (a != nullptr) a->to_string();
}
Feel free to refactor the whole std::visit
expression into a template function:
template <typename T,typename V>
T* visit2(V& variant){
return std::visit(shared_cast<T>,variant);
}