在玩std::variant
和std::visit
时,出现了以下问题:
考虑以下代码:
using Variant = std::variant<int, float, double>;
auto lambda = [](auto&& variant) {
std::visit(
[](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "int\n";
} else if (std::is_same_v<T, float>) {
std::cout << "float\n";
} else {
std::cout << "double\n";
}
},
variant);
};
它可以正常工作,如以下示例所示:
lambda(Variant(4.5)); // double
lambda(Variant(4.f)); // float
lambda(Variant(4)); // int
然后为什么以下操作失败:
using Variant = std::variant<int, float, double>;
auto lambda = [](auto&& variant) {
std::visit([](auto&& arg) { return arg; }, variant);
};
auto t = lambda(Variant(4.5));
由于静态断言
static_assert failed due to requirement '__all<is_same_v<int
(*)(__value_visitor<(lambda at main.cc:25:7)> &&,
__base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
double> &), float (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
__base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
double> &)>, is_same_v<int (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
__base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
double> &), double (*)(__value_visitor<(lambda at main.cc:25:7)> &&,
__base<std::__1::__variant_detail::_Trait::_TriviallyAvailable, int, float,
double> &)> >::value' "`std::visit` requires the visitor to have a single
return type."
如成功示例所示, std::visit
显然可以推论arg
的类型。那为什么要要求一个单一的返回类型呢?
编译器为Apple LLVM version 10.0.1 (clang-1001.0.46.4)
,但gcc version 8.3.0
失败并出现类似错误。
答案 0 :(得分:7)
std::visit
的返回类型仅取决于访问者的类型以及传递给它的变量。这就是C ++类型系统的工作原理。
如果您希望std::visit
返回一个值,则该值必须已经在编译时具有类型,因为在C ++中所有变量和表达式都具有静态类型。
您在该特定行中传递了Variant(4.5)
(因此,“显然访问将返回双精度”)这一事实不允许编译器改变类型系统的规则-{{1} } return type 根本无法根据您传递的变体 value 进行更改,并且不可能仅根据 type 来确定一种返回类型访问者的名称和变体的类型。其他一切都会带来极其奇怪的后果。
This维基百科文章实际上只是用std::visit
而不是更复杂的if
版本讨论了您所遇到的确切情况/问题:
例如,考虑一个包含以下代码的程序:
std::visit
即使表达式在运行时始终为true,大多数类型检查器也会将程序拒绝为错误类型,因为静态分析器很难(如果不是不可能)确定else分支不会被执行。
如果您希望返回的类型是“ variish-ish”,则必须坚持使用if <complex test> then <do something> else <signal that there is a type error>
。例如,您仍然可以:
std::variant
推论得出的auto rotateTypes = [](auto&& variant) {
return std::visit(
[](auto&& arg) -> std::variant<int, float, double> {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
return float(arg);
} else if (std::is_same_v<T, float>) {
return double(arg);
} else {
return int(arg);
}
},
variant);
};
的返回类型为std::visit
-只要您不决定一种类型,就必须留在一个变量中(或在单独的模板实例中)。您不能“欺骗” C ++放弃使用变体的身份访问者进行静态输入。
答案 1 :(得分:4)
尽管每个“实现”都是不同的重载,因此可能具有不同的返回类型,但是在某些时候,您将需要一个公共的访问点,而该公共的访问点将需要一个返回类型,因为选择的变体类型仅在运行时已知。
访问者通常会在visit
代码中执行该逻辑;实际上,std::visit
的真正目的是为您做所有的魔术,并抽象出运行时类型切换。
否则,您基本上会陷入在呼叫站点重新实现std::visit
的麻烦。
很容易想到可以使用模板解决所有问题:毕竟,您已经使用了通用lambda,因此所有这些重载都可以自动实例化,所以为什么不能仅将返回类型“已知”呢?同样,它仅在运行时才知道,所以这对您不利。必须有一些将访问结果传递给呼叫站点的静态方法。