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


10

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

我怎样才能解决这个问题?


3
我怀疑您遇到的错误与索引不是一个常量表达式有关。请发布编译器错误消息,以便我们提供有意义的帮助。
patatahooligan

缺少constexpr?
Rlyeh

哎呀!您谈论了一个错误,但没有发布错误的确切文本。
乔纳森·伍德

1
抱歉,遗漏了,我更新了问题
亚历克斯(Alex)

Answers:


4

本质上,您不能。

你写了:

我不知道里面有什么,但幸运的是,变体确实

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

成语用于模拟运行时数字模板参数?


@Caleth:是的。编辑。
einpoklum

5

编译器需要知道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 :: visit


3

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

所以一个constexpr价值

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

但是要初始化idxconstexpr,也var必须constexpr

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

…而且constexpr的变体不是很大。
戴维斯·鲱鱼

@DavisHerring-也是如此。
max66

2

问题是由于模板是在编译时实例化的,而要获取的索引是在运行时计算的。同样,C ++类型也是在编译时定义的,因此即使使用auto声明,C ++类型也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);
}

您可以使用visitor模式定义这些分支,请参阅std :: visit


1

这在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 提案,该提案允许通过f在调用它们时编译这些其他版本来允许类似这样的事情,但这还为时过早。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.