¿Por qué no puedo recuperar el índice de una variante y usarlo para obtener su contenido?

10

Estoy tratando de acceder al contenido de una variante. No sé qué hay allí, pero afortunadamente, la variante sí. Entonces pensé en preguntarle a la variante en qué índice está y luego usar ese índice parastd::get su contenido.

Pero esto no 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;
}

El error ocurre en la std::getllamada:

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ómo puedo arreglar esto?

Alex
fuente
3
Sospecho que el error que está recibiendo está relacionado con que el índice no sea una expresión constante. Publique los mensajes de error del compilador para que podamos brindarle ayuda significativa.
patatahooligan
¿Falta constexpr?
Rlyeh
Whoops! Usted habló sobre un error, pero no publicó el texto exacto del error.
Jonathan Wood
1
Perdón por la omisión, actualicé la pregunta
Alex

Respuestas:

4

Esencialmente, no puedes.

Tu escribiste:

No sé qué hay allí, pero afortunadamente, la variante sí

... pero solo en tiempo de ejecución, no en tiempo de compilación.
Y eso significa que su idxvalor no es tiempo de compilación.
Y eso significa que no puedes usarget<idx>() directamente.

Algo que podría hacer es tener una declaración de cambio; feo, pero funcionaría:

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

Sin embargo, esto es bastante feo. Como sugieren los comentarios, usted también podría std::visit()(que no es muy diferente del código anterior, excepto usar argumentos de plantilla variables en lugar de ser tan explícito) y evitar el cambio por completo. Para otros enfoques basados ​​en índices (no específicos destd::variant ), consulte:

¿Idioma para simular parámetros de plantilla numérica en tiempo de ejecución?

einpoklum
fuente
@Caleth: Sí Editado
einpoklum
5

El compilador necesita saber el valor idxen el momento de la compilación parastd::get<idx>() trabajar, ya que se está utilizando como argumento de plantilla.

Primera opción: si el código debe ejecutarse en tiempo de compilación, haga todo constexpr:

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

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

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

Esto funciona porque std::variantes constexpramigable (sus constructores y métodos son todosconstexpr ).

Segunda opción: si el código no está destinado a ejecutarse en tiempo de compilación, lo cual es probable, el compilador no puede deducir en tiempo de compilación el tipo de res, porque podría ser tres cosas diferentes ( int, floato char). C ++ es un lenguaje de tipo estático, y el compilador debe poder deducir el tipo de a auto res = ...partir de la expresión que sigue (es decir, siempre debe ser del mismo tipo).

Puede usar std::get<T>, con el tipo en lugar de un índice, si ya sabe cuál será:

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

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

En general, use std::holds_alternativepara verificar si la variante contiene cada uno de los tipos dados y manejarlos por separado:

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 puedes usar std::visit. Esto es un poco más complicado: puede usar una función lambda / plantilla que sea independiente del tipo y funcione para todos los tipos de variantes, o pasar un functor con un operador de llamada sobrecargado:

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

Ver std :: visit para detalles y ejemplos.

elbrunovsky
fuente
3

El problema es que std::get<idx>(var);requieren (para idx) un valor conocido de tiempo de compilación.

Entonces un constexprvalor

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

Pero para inicializar idxcomo constexpr, también vartenía que serconstexpr

// VVVVVVVVV
   constexpr std::variant<int, float, char> var { 42.0F };
max66
fuente
... Y una variante constexpr no es muy variante.
Davis Herring
@DavisHerring: eso también es cierto.
max66
2

El problema surge de la creación de instancias de plantillas en tiempo de compilación, mientras que el índice que está obteniendo se calcula en tiempo de ejecución. Del mismo modo, los tipos de C ++ también se definen en tiempo de compilación, por lo que incluso con la autodeclaración, resdebe tener un tipo concreto para que el programa esté bien formado. Esto significa que incluso sin la restricción en la plantilla, lo que está tratando de hacer es inherentemente imposible para expresiones no constantes std::variant. ¿Cómo se solucionaría esto?

En primer lugar, si su variante es una expresión constante, el código se compila y funciona como se esperaba

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

De lo contrario, deberá utilizar algún mecanismo de ramificación manual.

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

Puede definir estas ramas utilizando el patrón de visitante, consulte std :: visit .

patatahooligan
fuente
1

Esto es inherentemente imposible en el 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);
}

¿Cuál fse llama f<int>o f<double>? Si es "ambos", eso significa que gcontiene una rama (que no lo hace), o que hay dos versiones de g(que simplemente empuja el problema a su llamador). Y piense: f(T,U,V,W)¿dónde se detiene el compilador?

En realidad, hay una propuesta para un JIT para C ++ que permitiría cosas como esta compilando esas versiones adicionales de fcuándo se llaman, pero es muy temprano.

Arenque Davis
fuente