为什么 std::visit 必须有一个 return 类型?
Why must std::visit have a single return type?
在研究 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
的类型,如成功示例所示。那为什么要求只有一个 return 类型呢?
编译器 Apple LLVM version 10.0.1 (clang-1001.0.46.4)
但 gcc version 8.3.0
失败并出现类似错误。
尽管每个 "implementation" 都是不同的重载,因此可能具有不同的 return 类型,但在某些时候您将需要一个公共访问点,而该公共访问点将需要一个单一 return 类型,因为所选变体类型仅在运行时已知。
访问者在 visit
代码中执行该逻辑是常见的约定;事实上,std::visit
的真正目的就是为您施展魔法并抽象出运行时类型切换。
否则,您基本上会在调用站点重新实现 std::visit
。
很容易认为这一切都可以使用模板来解决:毕竟,您已经使用了通用的 lambda,所以所有这些重载都是自动实例化的,那么为什么 return 类型不能只是 "known"?同样,它只在运行时才知道,所以这对你没有好处。必须有某种静态方式将访问结果传送到调用站点。
std::visit
的 return 类型仅取决于访问者的类型和传递给它的变体。这就是 C++ 类型系统的工作原理。
如果您想要 std::visit
到 return 一个值,该值需要已经有一个 compile-time 的类型,因为在 C++ 中所有变量和表达式都是静态类型。
您在该特定行中传递 Variant(4.5)
(因此“显然访问将 return 是双精度”)这一事实不允许编译器改变类型系统的规则- std::visit
return type 根本无法根据您传递的变体 value 进行更改,并且无法决定仅在访问者的 type 和变体的 type 中的一个 return 类型上。其他一切都会产生极其奇怪的后果。
This 维基百科文章实际上基本上讨论了您所拥有的确切 situation/question,只是使用 if
而不是更详尽的 std::visit
版本:
For example, consider a program containing the code:
if <complex test> then <do something> else <signal that there is a type error>
Even if the expression always evaluates to true at run-time, most type checkers will reject the program as ill-typed, because it is difficult (if not impossible) for a static analyzer to determine that the else branch will not be taken.
如果您希望 returned 类型为“variant-ish”,您必须坚持使用 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
推导出的 return 类型是 std::variant<int, float, double>
- 只要你不决定一种类型,你必须留在一个变体中(或在单独的模板中)实例化)。您不能“欺骗”C++ 放弃在变体上使用 identity-visitor 的静态类型。
在研究 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
的类型,如成功示例所示。那为什么要求只有一个 return 类型呢?
编译器 Apple LLVM version 10.0.1 (clang-1001.0.46.4)
但 gcc version 8.3.0
失败并出现类似错误。
尽管每个 "implementation" 都是不同的重载,因此可能具有不同的 return 类型,但在某些时候您将需要一个公共访问点,而该公共访问点将需要一个单一 return 类型,因为所选变体类型仅在运行时已知。
访问者在 visit
代码中执行该逻辑是常见的约定;事实上,std::visit
的真正目的就是为您施展魔法并抽象出运行时类型切换。
否则,您基本上会在调用站点重新实现 std::visit
。
很容易认为这一切都可以使用模板来解决:毕竟,您已经使用了通用的 lambda,所以所有这些重载都是自动实例化的,那么为什么 return 类型不能只是 "known"?同样,它只在运行时才知道,所以这对你没有好处。必须有某种静态方式将访问结果传送到调用站点。
std::visit
的 return 类型仅取决于访问者的类型和传递给它的变体。这就是 C++ 类型系统的工作原理。
如果您想要 std::visit
到 return 一个值,该值需要已经有一个 compile-time 的类型,因为在 C++ 中所有变量和表达式都是静态类型。
您在该特定行中传递 Variant(4.5)
(因此“显然访问将 return 是双精度”)这一事实不允许编译器改变类型系统的规则- std::visit
return type 根本无法根据您传递的变体 value 进行更改,并且无法决定仅在访问者的 type 和变体的 type 中的一个 return 类型上。其他一切都会产生极其奇怪的后果。
This 维基百科文章实际上基本上讨论了您所拥有的确切 situation/question,只是使用 if
而不是更详尽的 std::visit
版本:
For example, consider a program containing the code:
if <complex test> then <do something> else <signal that there is a type error>
Even if the expression always evaluates to true at run-time, most type checkers will reject the program as ill-typed, because it is difficult (if not impossible) for a static analyzer to determine that the else branch will not be taken.
如果您希望 returned 类型为“variant-ish”,您必须坚持使用 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
推导出的 return 类型是 std::variant<int, float, double>
- 只要你不决定一种类型,你必须留在一个变体中(或在单独的模板中)实例化)。您不能“欺骗”C++ 放弃在变体上使用 identity-visitor 的静态类型。