Warum kann ich den Index einer Variante nicht abrufen und damit den Inhalt abrufen?

10

Ich versuche, auf den Inhalt einer Variante zuzugreifen. Ich weiß nicht, was da drin ist, aber zum Glück tut es die Variante. Also dachte ich, ich frage die Variante einfach, auf welchem ​​Index sie sich befindet, und verwende diesen Index dann für std::getihren Inhalt.

Dies kompiliert aber nicht:

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

Der Fehler tritt beim std::getAufruf auf:

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

Wie kann ich das beheben?

Alex
quelle
3
Ich vermute, der Fehler, den Sie erhalten, hängt damit zusammen, dass der Index kein konstanter Ausdruck ist. Bitte posten Sie die Compiler-Fehlermeldungen, damit wir sinnvolle Hilfe leisten können.
Patatahooligan
Vermisst constexpr?
Rlyeh
Hoppla! Sie haben über einen Fehler gesprochen, aber den genauen Text des Fehlers nicht veröffentlicht.
Jonathan Wood
1
Entschuldigung für die Auslassung, ich habe die Frage aktualisiert
Alex

Antworten:

4

Im Wesentlichen können Sie nicht.

Sie schrieben:

Ich weiß nicht, was da drin ist, aber zum Glück tut es die Variante

... aber nur zur Laufzeit, nicht zur Kompilierungszeit.
Und das bedeutet, dass Ihr idxWert keine Kompilierungszeit hat.
Und das bedeutet, dass Sie nicht get<idx>()direkt verwenden können.

Sie können eine switch-Anweisung verwenden. hässlich, aber es würde funktionieren:

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

Das ist jedoch ziemlich hässlich. Wie aus den Kommentaren hervorgeht, können Sie std::visit()den Wechsel auch ganz vermeiden (was sich nicht wesentlich vom obigen Code unterscheidet, außer dass Sie verschiedene Vorlagenargumente verwenden, anstatt dies explizit zu tun). Weitere indexbasierte Ansätze (nicht spezifisch für std::variant) finden Sie unter:

Redewendung zur Simulation numerischer Laufzeitvorlagenparameter?

einpoklum
quelle
@ Caleth: Ja. Bearbeitet.
Einpoklum
5

Der Compiler muss den Wert von idxzum Zeitpunkt der Kompilierung kennen, damit std::get<idx>()er funktioniert, da er als Vorlagenargument verwendet wird.

Erste Option: Wenn der Code zur Kompilierungszeit ausgeführt werden soll, machen Sie alles constexpr:

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

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

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

Dies funktioniert, weil std::variantes constexprfreundlich ist (seine Konstruktoren und Methoden sind alle constexpr).

Zweite Möglichkeit: Wenn der Code nicht gemeint ist , bei der Kompilierung laufen, was wahrscheinlich der Fall ist, kann der Compiler bei der Kompilierung nicht ableiten , welche Art von res, weil es drei verschiedenen Dinge sein könnte ( int, floatoder char). C ++ ist eine statisch typisierte Sprache, und der Compiler muss in der Lage sein, den Typ von auto res = ...aus dem folgenden Ausdruck abzuleiten (dh es muss immer der gleiche Typ sein).

Sie können std::get<T>den Typ anstelle eines Index verwenden, wenn Sie bereits wissen, wie er aussehen wird:

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

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

std::holds_alternativeÜberprüfen Sie im Allgemeinen, ob die Variante jeden der angegebenen Typen enthält, und behandeln Sie sie separat:

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

Alternativ können Sie verwenden std::visit. Dies ist etwas komplizierter: Sie können eine Lambda / Templated-Funktion verwenden, die typunabhängig ist und für alle Typen der Variante funktioniert, oder einen Funktor mit einem überlasteten Anrufoperator übergeben:

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

Siehe std :: visit für Details und Beispiele.

Elbrunovsky
quelle
3

Das Problem ist, std::get<idx>(var);dass (für idx) einen bekannten Wert zur Kompilierungszeit erforderlich ist.

Also ein constexprWert

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

Aber initialisieren idxals constexprauch varsein mussteconstexpr

// VVVVVVVVV
   constexpr std::variant<int, float, char> var { 42.0F };
max66
quelle
… Und eine constexpr-Variante ist nicht sehr variantenreich.
Davis Herring
@DavisHerring - Das stimmt auch.
Max66
2

Das Problem tritt auf, wenn Vorlagen zur Kompilierungszeit instanziiert werden, während der Index, den Sie erhalten, zur Laufzeit berechnet wird. In ähnlicher Weise werden C ++ - Typen auch zur Kompilierungszeit definiert, sodass sie auch mit der autoDeklaration reseinen konkreten Typ haben müssen, damit das Programm gut geformt ist. Dies bedeutet, dass das, was Sie versuchen, auch ohne die Einschränkung der Vorlage für nicht konstante Ausdrücke von Natur aus unmöglich ist std::variant. Wie würde man das umgehen?

Erstens, wenn Ihre Variante tatsächlich ein konstanter Ausdruck ist, wird der Code kompiliert und funktioniert wie erwartet

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

Andernfalls müssen Sie einen manuellen Verzweigungsmechanismus verwenden

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

Sie können diese Zweige anhand des Besuchermusters definieren, siehe std :: visit .

Patatahooligan
quelle
1

Dies ist im C ++ - Modell von Natur aus unmöglich. Erwägen

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

Welche f heißt f<int>oder f<double>? Wenn es "beides" ist, bedeutet dies, dass es geinen Zweig enthält (was nicht der Fall ist) oder dass es zwei Versionen von gibt g(was das Problem nur auf den Aufrufer überträgt). Und denken Sie darüber nach - f(T,U,V,W)wo hört der Compiler auf?

Es gibt tatsächlich einen Vorschlag für eine JIT für C ++, die solche Dinge ermöglicht, indem diese zusätzlichen Versionen kompiliert werden, fwenn sie aufgerufen werden, aber es ist sehr früh.

Davis Herring
quelle