为什么我不能检索变体的索引并使用它来获取其内容?

Why can I not retrieve the index of a variant and use that to get its content?

本文关键字:获取 检索 不能 索引 为什么      更新时间:2023-10-16

>我正在尝试访问变体的内容。我不知道里面有什么,但值得庆幸的是,变体确实如此。所以我想我只会问变体它在什么索引上,然后使用该索引来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();
^~~

我该如何解决这个问题?

编译器需要在编译时知道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::variantconstexpr友好(它的构造函数和方法都是constexpr的(。

第二种选择:如果代码不是在编译时运行的,那么编译器无法在编译时推断出res的类型,因为它可能是三种不同的东西(intfloatchar(。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/模板化函数,或者使用重载调用运算符传递函子:

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::visita 了解详细信息和示例。

本质上,你不能。

你写道:

我不知道里面有什么,但谢天谢地,变体确实如此

。但仅在运行时,而不是在编译时。
这意味着您的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(,请参阅:

模拟运行时数字模板参数的习语?

问题是std::get<idx>(var);需要(对于idx(编译时已知值。

所以constexpr价值

// VVVVVVVVV
constexpr std::size_t idx = var.index();

但是要初始化idxconstexprvar也必须constexpr

// VVVVVVVVV
constexpr std::variant<int, float, char> var { 42.0F };

问题源于模板在编译时实例化,而您获得的索引是在运行时计算的。同样,C++类型也是在编译时定义的,因此即使使用auto声明,res也必须具有具体类型才能使程序格式正确。这意味着即使没有对模板的限制,您尝试执行的操作对于非常量表达式std::variants本质上是不可能的。如何解决这个问题?

首先,如果您的变体确实是一个常量表达式,则代码将按预期编译和工作

#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。

这在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)——编译器在哪里停止?

实际上有一个关于 JIT for C++ 的建议,通过在调用时编译这些附加版本的f来允许这样的事情,但现在还为时过早。