问题描述
我想把我的头放在 std::variant
和 std::visit
上,我想提出一种方法来指定我希望我的变量保留的几种类型(这将进入我的 std::variant
),然后通过 std::visit
检索存储的数据。考虑以下示例:
#include <iostream>
#include <variant>
#include <string>
struct PrintType {
void operator()(const int &data) {
std::cout << "visiting int node" << std::endl;
}
void operator()(const double &data) {
std::cout << "visiting double node" << std::endl;
}
};
struct SingleOperatorOverload {
int operator()(const int &data) {
std::cout << "visiting int node" << std::endl;
return data;
}
};
struct AllTypesOperatorOverload {
int operator()(const int &data) {
std::cout << "visiting int node" << std::endl;
return data;
}
double operator()(const double &data) {
std::cout << "visiting double node" << std::endl;
return data;
}
};
int main() {
using var_t = std::variant<int,double>;
// print int related operator() content,OK
var_t foo = 42;
std::visit(PrintType(),foo);
// print double related operator() content,OK
foo = 3.1415;
std::visit(PrintType(),foo);
// get value and store into bar,struct with single operator(),OK
foo = 42;
auto bar = std::visit(SingleOperatorOverload(),foo);
std::cout << "bar: " << bar << std::endl;
// get value and store into bar,struct with multiple operator(),ERROR
auto bar = std::visit(AllTypesOperatorOverload(),foo);
std::cout << "bar: " << bar << std::endl;
return 0;
}
允许变体(在此简化示例中)保持 int
或 double
。如果我只想根据类型打印某些内容(如使用 PrintType
结构所做的那样),那工作正常。
如果我想通过访问者检索数据,就像在 SingleOperatorOverload
类中所做的那样,该类只为 operator()
提供了一个接受 int 作为参数的实现,这是可行的。但是,一旦我尝试为 operator()
中的每种类型实现一个 std::variant
,即这里的 int
和 double
,就像在 AllTypesOperatorOverload
结构中一样,我收到编译错误 error: invalid conversion from '...' {aka double ...} to '...' {aka int ...}
,所以似乎 std::variant
处理函数签名的方式不同?
我尝试过 SFINAE 但这似乎并没有缓解问题
struct AllTypesOperatorOverload {
template<typename T,std::enable_if_t<std::is_same<T,int>::value>>
T operator()(const T &data) {
std::cout << "visiting int node" << std::endl;
return data;
}
template<typename T,double>::value>>
T operator()(const T &data) {
std::cout << "visiting double node" << std::endl;
return data;
}
};
现在将报告 error: no type named 'type' in 'struct std::invoke_result<AllTypesOperatorOverload,int&>'
。有没有办法为所有类型提供 operator()
,然后根据 bar
的设置方式将它们各自的值以正确的类型接收到 foo
中?我知道 std::get_if<T>()
可能在这里有用,但理想情况下,除非绝对必要,否则我不想对每种类型进行长时间的 if 语句检查(这是一个简化示例,我可能想要我的 std::variant
中还有几种类型)。
解决方法
错误消息很糟糕,但这里的问题是变体的所有替代品在访问者中必须具有相同的返回类型。您的 if (strcmp(argv[argc-1],"1234") != 0)
不遵守此规则,返回 AllTypesOperatorOverload
和 double
,它们的类型不同。
最新版本的 libstdc++ or any version of libc++ produce much better error messages 明确告诉您这一点(以下是我自己包装的):
int
这是有道理的,因为当您查看此行时,error: static_assert failed due to requirement '__visit_rettypes_match'
"std::visit requires the visitor to have the same return type for
all alternatives of a variant"
static_assert(__visit_rettypes_match,
的类型是什么?
bar
如果允许您返回不同的类型,auto bar = std::visit(AllTypesOperatorOverload(),foo);
的类型将取决于运行时保留的替代 bar
。这在 C++ 中是行不通的。
请注意,有更简单的方法可以为使用 lambda 而非外部定义结构的 foo
创建访问者。您可以使用 std::visit
:
if constexpr
或者,您可以定义一个 std::visit([](auto value) {
if constexpr (std::is_same_v<int,decltype(value)>) {
std::cout << "visiting int\n";
} else {
static_assert(std::is_same_v<double,decltype(value)>);
std::cout << "visiting double\n";
}
std::cout << "bar: " << value << '\n';
},foo);
辅助结构,让您重载 lambda:
overloaded