Pourquoi ne puis-je pas récupérer l'index d'une variante et l'utiliser pour obtenir son contenu?

10

J'essaie d'accéder au contenu d'une variante. Je ne sais pas ce qu'il y a dedans, mais heureusement, la variante le fait. J'ai donc pensé demander à la variante sur quel index il se trouve, puis utiliser cet index pour std::getson contenu.

Mais cela ne compile pas:

#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'erreur se produit dans l' std::getappel:

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

Comment puis-je réparer cela?

Alex
la source
3
Je soupçonne que l'erreur que vous obtenez est liée au fait que l'index n'est pas une expression constante. Veuillez publier les messages d'erreur du compilateur afin que nous puissions fournir une aide significative.
patatahooligan
Constexpr manquant?
Rlyeh
Oups! Vous avez parlé d'une erreur, mais vous n'avez pas publié le texte exact de l'erreur.
Jonathan Wood
1
Désolé pour l'omission, j'ai mis à jour la question
Alex

Réponses:

4

Essentiellement, vous ne pouvez pas.

Tu as écrit:

Je ne sais pas ce qu'il y a dedans, mais heureusement, la variante ne

... mais uniquement au moment de l'exécution, pas au moment de la compilation.
Et cela signifie que votre idxvaleur n'est pas au moment de la compilation.
Et cela signifie que vous ne pouvez pas utiliser get<idx>()directement.

Quelque chose que vous pourriez faire est d'avoir une instruction switch; moche, mais ça marcherait:

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

C'est plutôt moche cependant. Comme le suggèrent les commentaires, vous pourriez aussi bien std::visit()(ce qui n'est pas très différent du code ci-dessus, sauf utiliser des arguments de modèles variadiques au lieu d'être aussi explicites) et éviter complètement le commutateur. Pour d'autres approches indexées (non spécifiques à std::variant), voir:

Idiome pour simuler des paramètres de modèle numérique d'exécution?

einpoklum
la source
@Caleth: Oui. Édité.
einpoklum
5

Le compilateur doit connaître la valeur de idxau moment de la compilation pour std::get<idx>()fonctionner, car il est utilisé comme argument de modèle.

Première option: si le code est destiné à être exécuté au moment de la compilation, alors faites tout constexpr:

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

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

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

Cela fonctionne car std::variantest constexprconvivial (ses constructeurs et méthodes sont tous constexpr).

Deuxième option: si le code n'est pas destiné à être exécuté au moment de la compilation, ce qui est probablement le cas, le compilateur ne peut pas déduire au moment de la compilation le type de res, car il peut s'agir de trois choses différentes ( int, floatou char). C ++ est un langage à typage statique, et le compilateur doit pouvoir déduire le type de auto res = ...de l'expression qui suit (c'est-à-dire qu'il doit toujours être du même type).

Vous pouvez utiliser std::get<T>, avec le type au lieu d'un index, si vous savez déjà de quoi il s'agit:

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

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

En général, utilisez std::holds_alternativepour vérifier si la variante contient chacun des types donnés et les gérer séparément:

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

Vous pouvez également utiliser std::visit. C'est un peu plus compliqué: vous pouvez utiliser une fonction lambda / basée sur un modèle qui est indépendante du type et fonctionne pour tous les types de la variante, ou passer un foncteur avec un opérateur d'appel surchargé:

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

Voir std :: visit pour les détails et les exemples.

elbrunovsky
la source
3

Le problème est qu'il std::get<idx>(var);faut (pour idx) une valeur connue au moment de la compilation.

Donc une constexprvaleur

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

Mais pour initialiser idxcomme constexpr, également vardû êtreconstexpr

// VVVVVVVVV
   constexpr std::variant<int, float, char> var { 42.0F };
max66
la source
… Et une variante constexpr n'est pas très variante.
Davis Herring
@DavisHerring - C'est également vrai.
max66
2

Le problème provient du fait que les modèles sont instanciés au moment de la compilation tandis que l'index que vous obtenez est calculé au moment de l'exécution. De même, les types C ++ sont également définis au moment de la compilation, donc même avec la autodéclaration, ils resdoivent avoir un type concret pour que le programme soit bien formé. Cela signifie que même sans la restriction sur le modèle, ce que vous essayez de faire est intrinsèquement impossible pour les expressions non constantes std::variant. Comment contourner cela?

Premièrement, si votre variante est en effet une expression constante, le code se compile et fonctionne comme prévu

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

Sinon, vous devrez utiliser un mécanisme de branchement manuel

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

Vous pouvez définir ces branches en utilisant le modèle de visiteur, voir std :: visit .

patatahooligan
la source
1

Ceci est intrinsèquement impossible dans le modèle de C ++; considérer

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

Qui fs'appelle, f<int>ou f<double>? Si c'est «les deux», cela signifie qu'il gcontient une branche (ce qui n'est pas le cas), ou qu'il existe deux versions de g(qui ne font que pousser le problème sur son appelant). Et pensez à: f(T,U,V,W)où s'arrête le compilateur?

Il y a en fait une proposition de JIT pour C ++ qui permettrait des choses comme ça en compilant ces versions supplémentaires de fquand elles sont appelées, mais c'est très tôt.

Davis Herring
la source