Perché non riesco a recuperare l'indice di una variante e utilizzarlo per ottenere il suo contenuto?


10

Sto provando ad accedere al contenuto di una variante. Non so cosa ci sia, ma per fortuna, la variante lo fa. Quindi ho pensato di chiedere alla variante su quale indice si trova e quindi utilizzare quell'indice per il std::getsuo contenuto.

Ma questo non viene compilato:

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

L'errore si verifica nella std::getchiamata:

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

Come posso risolvere questo problema?


3
Sospetto che l'errore che stai riscontrando sia correlato all'indice che non è un'espressione costante. Si prega di inviare i messaggi di errore del compilatore in modo che possiamo fornire un aiuto significativo.
patatahooligan,

Constexpr mancante?
Rlyeh,

Ops! Hai parlato di un errore, ma non hai pubblicato il testo esatto dell'errore.
Jonathan Wood,

1
Ci scusiamo per l'omissione, ho aggiornato la domanda
Alex

Risposte:


4

In sostanza, non puoi.

Hai scritto:

Non so cosa ci sia, ma per fortuna, la variante lo fa

... ma solo in fase di esecuzione, non in fase di compilazione.
Ciò significa che il tuo idxvalore non è tempo di compilazione.
E che i mezzi non è possibile utilizzare get<idx>()direttamente.

Qualcosa che potresti fare è avere un'istruzione switch; brutto, ma funzionerebbe:

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

Questo è piuttosto brutto comunque. Come suggeriscono i commenti, potresti anche std::visit()(che non è molto diverso dal codice sopra, tranne usare argomenti del modello variadico invece di essere esplicito) ed evitare del tutto il passaggio. Per altri approcci basati sull'indice (non specifici di std::variant), vedere:

Idioma per la simulazione dei parametri numerici del modello di runtime?


@Caleth: Sì. Modificato.
einpoklum,

5

idxPer std::get<idx>()poter funzionare, il compilatore deve conoscere il valore di al momento della compilazione , poiché viene utilizzato come argomento modello.

Prima opzione: se il codice deve essere eseguito in fase di compilazione, quindi eseguire tutto constexpr:

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

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

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

Questo funziona perché std::variantè constexpramichevole (i suoi costruttori e metodi sono tutti constexpr).

Seconda opzione: se il codice non deve essere eseguito in fase di compilazione, il che è probabilmente il caso, il compilatore non può dedurre in fase di compilazione il tipo di res, perché potrebbe essere tre cose diverse ( int, floato char). C ++ è un linguaggio tipicamente statico e il compilatore deve essere in grado di dedurre il tipo di auto res = ...dall'espressione che segue (cioè deve sempre essere dello stesso tipo).

Puoi utilizzare std::get<T>, con il tipo anziché un indice, se sai già quale sarà:

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

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

In generale, utilizzare std::holds_alternativeper verificare se la variante contiene ciascuno dei tipi indicati e gestirli separatamente:

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

In alternativa puoi usare std::visit. Questo è leggermente più complicato: puoi usare una funzione lambda / templated che è di tipo agnostico e funziona per tutti i tipi di variante o passare un funzione con un operatore di chiamata sovraccarico:

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

Vedi std :: visit per dettagli ed esempi.


3

Il problema è che std::get<idx>(var);richiedono (per idx) un valore noto in fase di compilazione.

Quindi un constexprvalore

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

Ma per inizializzare idxcome constexpr, vardoveva anche essereconstexpr

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

... E una variante constexpr non è molto variante.
Davis Herring,

@DavisHerring - Anche questo è vero.
max66,

2

Il problema sorge dal fatto che i modelli vengono istanziati in fase di compilazione mentre l'indice che si ottiene viene calcolato in fase di esecuzione. Allo stesso modo, anche i tipi di C ++ sono definiti in fase di compilazione, quindi anche con la autodichiarazione,res devono avere un tipo concreto affinché il programma sia ben formato. Ciò significa che anche senza la restrizione sul modello, ciò che si sta tentando di fare è intrinsecamente impossibile per le espressioni non costanti std::variant. Come si potrebbe aggirare questo?

Innanzitutto, se la tua variante è davvero un'espressione costante, il codice viene compilato e funziona come previsto

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

Altrimenti dovrai usare un meccanismo di ramificazione manuale

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

Puoi definire questi rami usando il modello visitatore, vedi std :: visit .


1

Ciò è intrinsecamente impossibile nel modello di C ++; prendere in considerazione

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

Quale fviene chiamato f<int>o f<double>? Se è "entrambi", significa che gcontiene un ramo (che non lo fa), o che ci sono due versioni di g(che spinge semplicemente il problema sul suo chiamante). E pensacif(T,U,V,W) dove si ferma il compilatore?

In realtà esiste una proposta per un JIT per C ++ che consentirebbe cose del genere compilando quelle versioni aggiuntive di fquando vengono chiamate, ma è molto presto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.