Reputation: 310
I have three classes, whereas an attribute of the first class can be the type of any of the other classes:
#include<variant>
class A {
public:
std::string to_string(){
return var.to_string();
}
std::variant<B,C> var;
};
class D {
virtual std::string to_string();
};
class B : public D {
public:
std::string to_string();
};
class C : public D {
public:
std::string to_string();
};
I want to invoke a method (lets say to_string) on the member of class A. I know that every variant has an implementation of the function.
The most effective I have come up with is from cppreference:
if(const B* b = std::get_if<B>(&var))
b->to_string();
if(const C* c = std::get_if<C>(&var))
c->to_string();
Especially when working with many variants, this seems ineffectiv and ugly and I am thinking that this should be possible with a one-liner. Is there a better way to do this?
Upvotes: 2
Views: 670
Reputation: 70277
As pointed out in the comments, since C
and B
inherit from D
, you can just store a std::unique_ptr<D>
(it has to be a pointer in order to get dynamic dispatch with object slicing). But if your real-world program has some constraints that prevent this from being viable, then your best friend when it comes to std::variant
is std::visit
.
std::visit
takes two arguments: a visitor and a variant. The visitor has to be an object of some form which is callable (i.e. has operator()
defined) for each variant option (in your case, B
and C
). Then it calls the appropriate operator()
for whatever is actually in the std::variant
.
We could make a new class that defines all of these callables for us. It'd be tedious.
class MyCallable {
std::string operator()(B& arg) {
return arg.to_string();
}
std::string operator()(C& arg) {
return arg.to_string();
}
};
...
std::visit(MyCallable(), var)
(Note: If you make to_string
on D
a const
method then you can take a const T&
rather than an lvalue reference, which is probably better here)
But that's silly and incredibly verbose. We could also template it. That cuts down on the boilerplate a bit.
class MyCallable {
template <typename T>
std::string operator()(T& arg) {
return arg.to_string();
}
};
...
std::visit(MyCallable(), var)
That's a bit better, but it's silly that we have to define a whole class for this. Fortunately, modern C++ will let you define lambdas which have templated operator()
, just like this but inline. So you can simply do this
std::visit([](auto& x) { return x.to_string(); }, var)
This will work for any std::variant
, provided all variant options have a compatible to_string
method (they don't even have to come from the same superclass; they can be unrelated to_string
methods).
Upvotes: 4