我有三个类,而第一个类的属性可以是任何其他类的类型:
#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();
};
我想在类a的成员上调用一个方法(比如to_string(。我知道每个变体都有函数的实现。
我想出的最有效的方法是来自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();
尤其是在处理许多变体时,这似乎是无效和丑陋的,我认为这应该可以通过一句话实现。有更好的方法吗?
正如注释中所指出的,由于C
和B
继承自D
,因此您只需存储一个std::unique_ptr<D>
(它必须是一个指针才能通过对象切片获得动态调度(。但是,如果你的现实世界中的程序有一些限制,使其无法实现,那么当涉及到std::variant
时,你最好的朋友就是std::visit
。
std::visit
接受两个参数:一个访问者和一个变体。访问者必须是某种形式的对象,对于每个变体选项(在您的情况下是B
和C
(,该对象是可调用的(即定义了operator()
(。然后,它为std::variant
中的实际调用适当的operator()
。
我们可以创建一个新的类,为我们定义所有这些可调用项。这会很乏味。
class MyCallable {
std::string operator()(B& arg) {
return arg.to_string();
}
std::string operator()(C& arg) {
return arg.to_string();
}
};
...
std::visit(MyCallable(), var)
(注意:如果您将D
上的to_string
设置为const
方法,那么您可以使用const T&
而不是左值引用,这可能在这里更好(
但这很愚蠢,而且非常冗长。我们也可以将其模板化。这样可以减少一些样板工作。
class MyCallable {
template <typename T>
std::string operator()(T& arg) {
return arg.to_string();
}
};
...
std::visit(MyCallable(), var)
这稍微好一点,但我们必须为此定义一个完整的类,这很愚蠢。幸运的是,现代C++将允许您定义模板化了operator()
的lambda,就像这样,但却是内联的。所以你可以简单地做这个
std::visit([](auto& x) { return x.to_string(); }, var)
这将适用于任何std::variant
,前提是所有变体选项都有兼容的to_string
方法(它们甚至不必来自同一个超类;它们可以是不相关的to_string
方法(。