C++
std :: variante
Ricerca…
Osservazioni
La variante è un rimpiazzo per l'utilizzo di una grezza union
. È sicuro dal tipo e sa di che tipo si tratta, e costruisce e distrugge attentamente gli oggetti al suo interno quando dovrebbe.
Non è quasi mai vuoto: solo nei casi d'angolo in cui il suo contenuto viene sostituito e non può essere ritirato in sicurezza, finisce per trovarsi in uno stato vuoto.
Si comporta in qualche modo come una std::tuple
, e in qualche modo come un std::optional
.
Usare std::get
e std::get_if
solito è una cattiva idea. La risposta corretta è solitamente std::visit
, che ti consente di gestire ogni possibilità proprio lì. if constexpr
può essere utilizzato all'interno della visit
se è necessario if constexpr
il comportamento, piuttosto che eseguire una sequenza di controlli di runtime che duplicano la visit
modo più efficiente.
Base std :: uso variante
Questo crea una variante (un'unione con tag) in grado di memorizzare una string
int
o una string
.
std::variant< int, std::string > var;
Possiamo memorizzare uno dei due tipi in esso:
var = "hello"s;
E possiamo accedere ai contenuti tramite std::visit
:
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
passando in un lambda polimorfo o oggetto di funzione simile.
Se siamo certi di sapere di che tipo si tratta, possiamo ottenerlo:
auto str = std::get<std::string>(var);
ma questo verrà buttato se ci sbagliamo. get_if
:
auto* str = std::get_if<std::string>(&var);
restituisce nullptr
se si indovina sbagliato.
Le varianti non garantiscono l'allocazione dinamica della memoria (diversa da quella assegnata dai loro tipi contenuti). Qui viene memorizzato solo uno dei tipi di una variante e, in rari casi (con l'eccezione di eccezioni durante l'assegnazione e nessun modo sicuro di uscire), la variante può diventare vuota.
Le varianti consentono di memorizzare più tipi di valore in una variabile in modo sicuro ed efficiente. Sono fondamentalmente intelligenti, di tipo sicuro union
s.
Crea puntatori pseudo-metodi
Questo è un esempio avanzato.
È possibile utilizzare la variante per cancellare il tipo di peso leggero.
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)
);
};
}
};
questo crea un tipo che sovraccarica l' operator->*
con una Variant
sul lato sinistro.
// 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)... );
};
Ora se abbiamo 2 tipi ciascuno con un metodo di print
:
struct A {
void print( std::ostream& os ) const {
os << "A";
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
si noti che sono tipi non correlati. Noi possiamo:
std::variant<A,B> var = A{};
(var->*print)(std::cout);
e invierà la chiamata direttamente ad A::print(std::cout)
per noi. Se invece inizializziamo la var
con B{}
, si invierà a B::print(std::cout)
.
Se abbiamo creato un nuovo tipo C:
struct C {};
poi:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
non riuscirà a compilare, perché non esiste un C.print(std::cout)
.
Estendere quanto sopra consentirebbe di rilevare e utilizzare le funzioni di print
libere, eventualmente con l'uso di if constexpr
all'interno dello pseudo-metodo di print
.
esempio live attualmente usando boost::variant
al posto di std::variant
.
Costruire un `std :: variant`
Questo non copre gli allocatori.
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}