C++
std :: variant
Sök…
Anmärkningar
Variant är en ersättning för rå union
användning. Den är typsäker och vet vilken typ den är, och den konstruerar och förstör försiktigt föremålen i den när den borde.
Det är nästan aldrig tomt: endast i hörnfall där utbyte av innehåll kastas och det inte kan ryggas ut säkert hamnar det i tomt tillstånd.
Det uppför sig något som en std::tuple
, och något som en std::optional
.
Att använda std::get
och std::get_if
är vanligtvis en dålig idé. Rätt svar är vanligtvis std::visit
, vilket gör att du kan hantera alla möjligheter där. if constexpr
kan användas under visit
om du behöver förgrena ditt beteende, snarare än att göra en sekvens av runtime-kontroller som duplicerar vad visit
kommer att göra mer effektivt.
Grundläggande std :: variantanvändning
Detta skapar en variant (en taggad union) som kan lagra antingen en int
eller en string
.
std::variant< int, std::string > var;
Vi kan lagra en av båda typerna i den:
var = "hello"s;
Och vi kan komma åt innehållet via std::visit
:
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
genom att passera in en polymorfisk lambda eller liknande funktionsobjekt.
Om vi är säkra på att vi vet vilken typ det är, kan vi få det:
auto str = std::get<std::string>(var);
men detta kommer att kasta om vi gör fel. get_if
:
auto* str = std::get_if<std::string>(&var);
returnerar nullptr
om du gissar fel.
Varianter garanterar ingen dynamisk minnesallokering (annat än som tilldelas av deras innehåll). Endast en av typerna i en variant lagras där, och i sällsynta fall (med undantag vid tilldelning och inget säkert sätt att backa ut) kan varianten bli tom.
Med varianter kan du lagra flera värdetyper i en variabel säkert och effektivt. De är i grunden smarta, typsäkra union
.
Skapa pseudo-metodpekare
Detta är ett avancerat exempel.
Du kan använda variant för radering av lätt vikt.
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)
);
};
}
};
detta skapar en typ som överbelaster operator->*
med en Variant
på vänster sida.
// 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)... );
};
Om vi nu har två typer med en print
:
struct A {
void print( std::ostream& os ) const {
os << "A";
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
Observera att de inte är relaterade typer. Vi kan:
std::variant<A,B> var = A{};
(var->*print)(std::cout);
och det kommer att skicka samtalet direkt till A::print(std::cout)
för oss. Om vi istället initialiserade var
med B{}
, skulle det skickas till B::print(std::cout)
.
Om vi skapade en ny typ C:
struct C {};
sedan:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
kommer inte att kompilera, eftersom det inte finns någon C.print(std::cout)
-metod.
Förlängning av ovan skulle tillåta fri funktion print
s som skall detekteras och användas, eventuellt med användning av if constexpr
inom print
pseudo-metoden.
levande exempel som för närvarande använder boost::variant
istället för std::variant
.
Konstruera en `std :: variant`
Detta täcker inte tilldelare.
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}