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

问题描述 投票:8回答:5

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

我该如何解决?

c++ c++17 variant
5个回答
4
投票

基本上,您不能。

您写道:

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

...,但仅在运行时,而不在编译时。这意味着您的idx值不是编译时。并且that表示您不能直接使用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),请参见:

Idiom for simulating run-time numeric template parameters?


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的类型,因为它可能是三个不同的东西(int,[ C0]或float)。 C ++是一种静态类型的语言,编译器必须能够从随后的表达式中推断出char的类型(即,它必须始终是同一类型)。

如果您已经知道它将是什么,则可以将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& // ... } 。这稍微复杂一点:您可以使用与类型无关的lambda /模板化函数,并且该函数适用于所有变体的类型,或者将函子与重载的调用运算符一起传递给函子:

std::visit

请参见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); 以获取详细信息和示例。


3
投票

问题在于,std::visit需要(对于std::get<idx>(var);)需要一个编译时已知值。

所以idx

constexpr

但是要将// VVVVVVVVV constexpr std::size_t idx = var.index(); 初始化为idx,也必须将constexpr设为var

constexpr

2
投票

问题是由于模板是在编译时实例化的,而要获取的索引是在运行时计算的。同样,C ++类型也在编译时定义,因此即使使用// VVVVVVVVV constexpr std::variant<int, float, char> var { 42.0F }; 声明,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); } 。>>


1
投票

这在C ++的模型中本质上是不可能的;考虑

© www.soinside.com 2019 - 2024. All rights reserved.