Por que não consigo recuperar o índice de uma variante e usá-lo para obter seu conteúdo?

10

Estou tentando acessar o conteúdo de uma variante. Não sei o que há lá, mas, felizmente, a variante sabe. Então, pensei em perguntar à variante em que índice ela está e depois usá-la em std::getseu conteúdo.

Mas isso não compila:

#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;
}

O erro ocorre na std::getchamada:

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();
               ^~~

Como posso consertar isso?

Alex
fonte
3
Suspeito que o erro que você está recebendo esteja relacionado ao índice não ser uma expressão constante. Poste as mensagens de erro do compilador para que possamos fornecer ajuda significativa.
Patatahooligan 29/11/19
Faltando constexpr?
rlyeh
Ops! Você falou sobre um erro, mas não publicou o texto exato do erro.
Jonathan Wood
11
Desculpe pela omissão, eu atualizei a pergunta #
286 Alex Alex

Respostas:

4

Essencialmente, você não pode.

Você escreveu:

Não sei o que há lá, mas, felizmente, a variante

... mas apenas no tempo de execução, não no tempo de compilação.
E isso significa que seu idxvalor não é em tempo de compilação.
E isso significa que você não pode usar get<idx>()diretamente.

Algo que você pode fazer é ter uma instrução switch; feio, mas funcionaria:

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.
}

Isso é bastante feio, no entanto. Como os comentários sugerem, você também pode std::visit()(que não é muito diferente do código acima, exceto usar argumentos de modelo variados em vez de ser explícito) e evitar a mudança completamente. Para outras abordagens baseadas em índice (não específicas para std::variant), consulte:

Idioma para simular parâmetros numéricos de modelo em tempo de execução?

einpoklum
fonte
@Caleth: Sim. Editado.
einpoklum
5

O compilador precisa saber o valor de idxno momento da compilação para std::get<idx>()funcionar, porque está sendo usado como argumento de modelo.

Primeira opção: se o código for executado em tempo de compilação, faça tudo constexpr:

constexpr std::variant<int, float, char> var { 42.0f };

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

constexpr auto res = std::get<idx>(var);

Isso funciona porque std::varianté constexpramigável (seus construtores e métodos são todos constexpr).

Segunda opção: se o código não for executado em tempo de compilação, o que provavelmente é o caso, o compilador não poderá deduzir em tempo de compilação o tipo de res, porque pode haver três coisas diferentes ( int, floatou char). C ++ é uma linguagem de tipo estaticamente, e o compilador deve poder deduzir o tipo de auto res = ...da expressão a seguir (ou seja, sempre deve ser o mesmo tipo).

Você pode usar std::get<T>, com o tipo em vez de um índice, se você já sabe o que será:

std::variant<int, float, char> var { 42.0f }; // chooses float

auto res = std::get<float>(var);

Em geral, use std::holds_alternativepara verificar se a variante está mantendo cada um dos tipos fornecidos e manipule-os separadamente:

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&
    // ...
}

Alternativamente, você pode usar std::visit. Isso é um pouco mais complicado: você pode usar uma função lambda / modelo que é independente de tipo e funciona para todos os tipos de variantes, ou passa um functor com um operador de chamada sobrecarregado:

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);

Veja std :: visit para detalhes e exemplos.

elbrunovsky
fonte
3

O problema é que std::get<idx>(var);requer (por idx) um valor conhecido em tempo de compilação.

Então um constexprvalor

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

Mas para inicializar idxcomo constexpr, também vartinha que serconstexpr

// VVVVVVVVV
   constexpr std::variant<int, float, char> var { 42.0F };
max66
fonte
... E uma variante constexpr não é muito variante.
Davis Herring
@ DavisHerring - Isso também é verdade.
max66
2

O problema surge quando os modelos são instanciados em tempo de compilação, enquanto o índice que você está obtendo é calculado em tempo de execução. Da mesma forma, os tipos C ++ também são definidos em tempo de compilação, portanto, mesmo com a autodeclaração, resdeve haver um tipo concreto para que o programa seja bem formado. Isso significa que, mesmo sem a restrição no modelo, o que você está tentando fazer é inerentemente impossível para expressões não constantes std::variants. Como se resolveria isso?

Em primeiro lugar, se sua variante é realmente uma expressão constante, o código compila e funciona conforme o esperado

#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;
}

Caso contrário, você terá que usar algum mecanismo de ramificação manual

if (idx == 0) {
    // Now 'auto' will have a concrete type which I've explicitly used
    int value == std::get<0>(var);
}

Você pode definir essas ramificações usando o padrão de visitante, consulte std :: visit .

patatahooligan
fonte
1

Isso é inerentemente impossível no modelo de C ++; considerar

template<class T> void f(T);
void g(std::variant<int,double> v) {
  auto x=std::get<v.index()>(v);
  f(x);
}

Qual festá sendo chamado f<int>ou f<double>? Se for "ambos", significa que gcontém um ramo (o que não existe) ou que existem duas versões g(o que apenas coloca o problema no chamador). E pense: f(T,U,V,W)onde o compilador para?

Na verdade, existe uma proposta para um JIT para C ++ que permitiria coisas assim, compilando essas versões adicionais de fquando elas são chamadas, mas é muito cedo.

Davis Herring
fonte