为什么我不能检索变体的索引并使用它来获取其内容?
Why can I not retrieve the index of a variant and use that to get its content?
我正在尝试访问变体的内容。我不知道里面有什么,但谢天谢地,变种确实知道。所以我想我会问变体它在什么索引上,然后使用该索引 std::get
它的内容。
但这不能编译:
#include <variant>
int main()
{
std::variant<int, float, char> var { 42.0F };
const std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
错误发生在 std::get
调用中:
error: no matching function for call to ‘get<idx>(std::variant<int, float, char>&)’
auto res = std::get<idx>(var);
^
In file included from /usr/include/c++/8/variant:37,
from main.cpp:1:
/usr/include/c++/8/utility:216:5: note: candidate: ‘template<long unsigned int _Int, class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)’
get(std::pair<_Tp1, _Tp2>& __in) noexcept
^~~
/usr/include/c++/8/utility:216:5: note: template argument deduction/substitution failed:
main.cpp:9:31: error: the value of ‘idx’ is not usable in a constant expression
auto res = std::get<idx>(var);
^
main.cpp:7:15: note: ‘std::size_t idx’ is not const
std::size_t idx = var.index();
^~~
我该如何解决这个问题?
问题是 std::get<idx>(var);
需要(对于 idx
)一个编译时已知值。
所以 constexpr
值
// VVVVVVVVV
constexpr std::size_t idx = var.index();
但是要将 idx
初始化为 constexpr
,var
也必须是 constexpr
// VVVVVVVVV
constexpr std::variant<int, float, char> var { 42.0F };
问题出在编译时实例化模板,而您获得的索引是在 运行 时计算的。类似地,C++ 类型也在编译时定义,因此即使使用 auto
声明,res
也必须具有具体类型才能使程序格式良好。这意味着即使没有对模板的限制,对于非常量表达式 std::variant
来说,你试图做的事情本质上是不可能的。如何解决这个问题?
首先,如果你的变体确实是一个常量表达式,代码会按预期编译和工作
#include <variant>
int main()
{
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
否则你将不得不使用一些手动分支机制
if (idx == 0) {
// Now 'auto' will have a concrete type which I've explicitly used
int value == std::get<0>(var);
}
您可以使用访问者模式定义这些分支,参见 std::visit。
基本上,你不能。
您写道:
I don't know what's in there, but thankfully, the variant does
...但仅在 运行 时,而不是在编译时。
这意味着您的 idx
值不是编译时。
而那意味着你不能直接使用get<idx>()
。
你可以做的是有一个 switch 语句;丑陋,但它会起作用:
switch(idx) {
case 0: { /* code which knows at compile time that idx is 0 */ } break;
case 1: { /* code which knows at compile time that idx is 1 */ } break;
// etc. etc.
}
然而,这相当丑陋。正如评论所建议的那样,您也可以 std::visit()
(这与上面的代码没有太大区别,除了使用可变参数模板参数而不是如此显式)并完全避免切换。对于其他基于索引的方法(不特定于 std::variant
),请参阅:
编译器需要在编译时知道 idx
的值,std::get<idx>()
才能工作,因为它被用作模板参数。
第一个选项:如果代码要在编译时 运行,则将所有内容设为 constexpr
:
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
constexpr auto res = std::get<idx>(var);
这是可行的,因为 std::variant
是 constexpr
友好的(它的构造函数和方法都是 constexpr
)。
第二个选项:如果代码在编译时不打算 运行(很可能是这种情况),编译器无法在编译时推断出 res
的类型,因为它可能是三个不同的东西(int
、float
或 char
)。 C++ 是一种静态类型语言,编译器必须能够从后面的表达式中推断出 auto res = ...
的类型(即它必须始终是同一类型)。
您可以使用 std::get<T>
和类型而不是索引,如果您已经知道它将是什么:
std::variant<int, float, char> var { 42.0f }; // chooses float
auto res = std::get<float>(var);
一般来说,使用std::holds_alternative
检查变体是否持有每个给定的类型,并分别处理它们:
std::variant<int, float, char> var { 42.0f };
if (std::holds_alternative<int>(var)) {
auto int_res = std::get<int>(var); // int&
// ...
} else if (std::holds_alternative<float>(var)) {
auto float_res = std::get<float>(var); // float&
// ...
} else {
auto char_res = std::get<char>(var); // char&
// ...
}
或者您可以使用 std::visit
。这稍微复杂一些:您可以使用与类型无关并适用于所有变体类型的 lambda/templated 函数,或者传递带有重载调用运算符的仿函数:
std::variant<int, float, char> var { 42.0f };
std::size_t idx = var.index();
std::visit([](auto&& val) {
// use val, which may be int&, float& or char&
}, var);
有关详细信息和示例,请参阅 std::visit。
这在C++的模型中是天生不可能的;考虑
template<class T> void f(T);
void g(std::variant<int,double> v) {
auto x=std::get<v.index()>(v);
f(x);
}
正在调用哪个 f
,f<int>
或 f<double>
?如果它是“两者”,则意味着 g
包含一个分支(它没有),或者 g
有两个版本(这只是将问题推给了它的调用者)。想想 f(T,U,V,W)
——编译器在哪里停止?
实际上有一个用于 C++ 的 JIT 的 proposal,它可以通过在调用 f
的那些附加版本时编译它们来允许这样的事情,但它还很早。
我正在尝试访问变体的内容。我不知道里面有什么,但谢天谢地,变种确实知道。所以我想我会问变体它在什么索引上,然后使用该索引 std::get
它的内容。
但这不能编译:
#include <variant>
int main()
{
std::variant<int, float, char> var { 42.0F };
const std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
错误发生在 std::get
调用中:
error: no matching function for call to ‘get<idx>(std::variant<int, float, char>&)’
auto res = std::get<idx>(var);
^
In file included from /usr/include/c++/8/variant:37,
from main.cpp:1:
/usr/include/c++/8/utility:216:5: note: candidate: ‘template<long unsigned int _Int, class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)’
get(std::pair<_Tp1, _Tp2>& __in) noexcept
^~~
/usr/include/c++/8/utility:216:5: note: template argument deduction/substitution failed:
main.cpp:9:31: error: the value of ‘idx’ is not usable in a constant expression
auto res = std::get<idx>(var);
^
main.cpp:7:15: note: ‘std::size_t idx’ is not const
std::size_t idx = var.index();
^~~
我该如何解决这个问题?
问题是 std::get<idx>(var);
需要(对于 idx
)一个编译时已知值。
所以 constexpr
值
// VVVVVVVVV
constexpr std::size_t idx = var.index();
但是要将 idx
初始化为 constexpr
,var
也必须是 constexpr
// VVVVVVVVV
constexpr std::variant<int, float, char> var { 42.0F };
问题出在编译时实例化模板,而您获得的索引是在 运行 时计算的。类似地,C++ 类型也在编译时定义,因此即使使用 auto
声明,res
也必须具有具体类型才能使程序格式良好。这意味着即使没有对模板的限制,对于非常量表达式 std::variant
来说,你试图做的事情本质上是不可能的。如何解决这个问题?
首先,如果你的变体确实是一个常量表达式,代码会按预期编译和工作
#include <variant>
int main()
{
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
否则你将不得不使用一些手动分支机制
if (idx == 0) {
// Now 'auto' will have a concrete type which I've explicitly used
int value == std::get<0>(var);
}
您可以使用访问者模式定义这些分支,参见 std::visit。
基本上,你不能。
您写道:
I don't know what's in there, but thankfully, the variant does
...但仅在 运行 时,而不是在编译时。
这意味着您的 idx
值不是编译时。
而那意味着你不能直接使用get<idx>()
。
你可以做的是有一个 switch 语句;丑陋,但它会起作用:
switch(idx) {
case 0: { /* code which knows at compile time that idx is 0 */ } break;
case 1: { /* code which knows at compile time that idx is 1 */ } break;
// etc. etc.
}
然而,这相当丑陋。正如评论所建议的那样,您也可以 std::visit()
(这与上面的代码没有太大区别,除了使用可变参数模板参数而不是如此显式)并完全避免切换。对于其他基于索引的方法(不特定于 std::variant
),请参阅:
编译器需要在编译时知道 idx
的值,std::get<idx>()
才能工作,因为它被用作模板参数。
第一个选项:如果代码要在编译时 运行,则将所有内容设为 constexpr
:
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
constexpr auto res = std::get<idx>(var);
这是可行的,因为 std::variant
是 constexpr
友好的(它的构造函数和方法都是 constexpr
)。
第二个选项:如果代码在编译时不打算 运行(很可能是这种情况),编译器无法在编译时推断出 res
的类型,因为它可能是三个不同的东西(int
、float
或 char
)。 C++ 是一种静态类型语言,编译器必须能够从后面的表达式中推断出 auto res = ...
的类型(即它必须始终是同一类型)。
您可以使用 std::get<T>
和类型而不是索引,如果您已经知道它将是什么:
std::variant<int, float, char> var { 42.0f }; // chooses float
auto res = std::get<float>(var);
一般来说,使用std::holds_alternative
检查变体是否持有每个给定的类型,并分别处理它们:
std::variant<int, float, char> var { 42.0f };
if (std::holds_alternative<int>(var)) {
auto int_res = std::get<int>(var); // int&
// ...
} else if (std::holds_alternative<float>(var)) {
auto float_res = std::get<float>(var); // float&
// ...
} else {
auto char_res = std::get<char>(var); // char&
// ...
}
或者您可以使用 std::visit
。这稍微复杂一些:您可以使用与类型无关并适用于所有变体类型的 lambda/templated 函数,或者传递带有重载调用运算符的仿函数:
std::variant<int, float, char> var { 42.0f };
std::size_t idx = var.index();
std::visit([](auto&& val) {
// use val, which may be int&, float& or char&
}, var);
有关详细信息和示例,请参阅 std::visit。
这在C++的模型中是天生不可能的;考虑
template<class T> void f(T);
void g(std::variant<int,double> v) {
auto x=std::get<v.index()>(v);
f(x);
}
正在调用哪个 f
,f<int>
或 f<double>
?如果它是“两者”,则意味着 g
包含一个分支(它没有),或者 g
有两个版本(这只是将问题推给了它的调用者)。想想 f(T,U,V,W)
——编译器在哪里停止?
实际上有一个用于 C++ 的 JIT 的 proposal,它可以通过在调用 f
的那些附加版本时编译它们来允许这样的事情,但它还很早。