C++
std :: variant
Suche…
Bemerkungen
Variante ist ein Ersatz für rohe union
Einsatz. Es ist typensicher und weiß, um welchen Typ es sich handelt, und es konstruiert und zerstört die Objekte in dem Objekt sorgfältig, wenn es sollte.
Es ist fast nie leer: Nur in Eckfällen, in denen das Ersetzen des Inhalts geworfen wird und er nicht sicher zurückkehren kann, endet er in einem leeren Zustand.
Es verhält sich etwas wie ein std::tuple
und etwas wie ein std::optional
.
Die Verwendung von std::get
und std::get_if
ist normalerweise eine schlechte Idee. Die richtige Antwort ist in der Regel std::visit
, so dass Sie direkt mit jeder Möglichkeit umgehen können. if constexpr
innerhalb des visit
werden kann, wenn Sie Ihr Verhalten verzweigen müssen, anstatt eine Laufzeitprüfung if constexpr
, wird überprüft, ob der visit
effizienter ist.
Grundsätzliche Verwendung von std :: variant
Dadurch wird eine Variante (eine markierte Union) erstellt, in der entweder ein int
oder ein string
gespeichert werden kann.
std::variant< int, std::string > var;
Wir können einen von beiden Typen darin speichern:
var = "hello"s;
Und wir können auf die Inhalte über std::visit
zugreifen:
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
durch Einleiten eines polymorphen Lambda oder eines ähnlichen Funktionsobjekts.
Wenn wir sicher sind, um welchen Typ es sich handelt, können wir ihn bekommen:
auto str = std::get<std::string>(var);
aber das wird werfen, wenn wir es falsch verstehen. get_if
:
auto* str = std::get_if<std::string>(&var);
nullptr
wenn Sie falsch raten.
Varianten garantieren keine dynamische Speicherzuweisung (anders als bei den enthaltenen Typen). Dort ist nur einer der Typen gespeichert, und in seltenen Fällen (mit Ausnahmen beim Zuweisen und ohne sichere Rücknahme) kann die Variante leer werden.
Mit Varianten können Sie mehrere Wertetypen sicher und effizient in einer Variablen speichern. Sie sind im Grunde intelligente, typsichere union
.
Erstellen Sie Pseudo-Methodenzeiger
Dies ist ein fortgeschrittenes Beispiel.
Sie können die Variante für das Löschen von Leichtgewichten verwenden.
template<class F>
struct pseudo_method {
F f;
// enable C++17 class type deduction:
pseudo_method( F&& fin ):f(std::move(fin)) {}
// Koenig lookup operator->*, as this is a pseudo-method it is appropriate:
template<class Variant> // maybe add SFINAE test that LHS is actually a variant.
friend decltype(auto) operator->*( Variant&& var, pseudo_method const& method ) {
// var->*method returns a lambda that perfect forwards a function call,
// behaving like a method pointer basically:
return [&](auto&&...args)->decltype(auto) {
// use visit to get the type of the variant:
return std::visit(
[&](auto&& self)->decltype(auto) {
// decltype(x)(x) is perfect forwarding in a lambda:
return method.f( decltype(self)(self), decltype(args)(args)... );
},
std::forward<Var>(var)
);
};
}
};
Dadurch wird ein Typ erstellt, der operator->*
mit einer Variant
auf der linken Seite überladen.
// C++17 class type deduction to find template argument of `print` here.
// a pseudo-method lambda should take `self` as its first argument, then
// the rest of the arguments afterwards, and invoke the action:
pseudo_method print = [](auto&& self, auto&&...args)->decltype(auto) {
return decltype(self)(self).print( decltype(args)(args)... );
};
Wenn wir nun 2 Typen mit einer print
:
struct A {
void print( std::ostream& os ) const {
os << "A";
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
Beachten Sie, dass es sich nicht um Typen handelt. Wir können:
std::variant<A,B> var = A{};
(var->*print)(std::cout);
Der Anruf wird direkt an A::print(std::cout)
für uns A::print(std::cout)
. Wenn wir das var
stattdessen mit B{}
initialisieren, wird es an B::print(std::cout)
.
Wenn wir einen neuen Typ C erstellt haben:
struct C {};
dann:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
wird nicht kompiliert, da es keine C.print(std::cout)
-Methode gibt.
Die oben erstreckt würde freie Funktion erlauben print
s erkannt und verwendet werden, gegebenenfalls unter Verwendung von , if constexpr
innerhalb des print
pseudo-Methode.
Live-Beispiel, das momentan boost::variant
anstelle von std::variant
.
Konstruktion einer `std :: variant`
Dies gilt nicht für die Zuteiler.
struct A {};
struct B { B()=default; B(B const&)=default; B(int){}; };
struct C { C()=delete; C(int) {}; C(C const&)=default; };
struct D { D( std::initializer_list<int> ) {}; D(D const&)=default; D()=default; };
std::variant<A,B> var_ab0; // contains a A()
std::variant<A,B> var_ab1 = 7; // contains a B(7)
std::variant<A,B> var_ab2 = var_ab1; // contains a B(7)
std::variant<A,B,C> var_abc0{ std::in_place_type<C>, 7 }; // contains a C(7)
std::variant<C> var_c0; // illegal, no default ctor for C
std::variant<A,D> var_ad0( std::in_place_type<D>, {1,3,3,4} ); // contains D{1,3,3,4}
std::variant<A,D> var_ad1( std::in_place_index<0> ); // contains A{}
std::variant<A,D> var_ad2( std::in_place_index<1>, {1,3,3,4} ); // contains D{1,3,3,4}