C++
std :: wariant
Szukaj…
Uwagi
Wariant jest zamiennikiem surowego zastosowania union
. Jest bezpieczny dla typu i wie, jaki to jest typ, i ostrożnie konstruuje i niszczy znajdujące się w nim obiekty, kiedy powinien.
Prawie nigdy nie jest pusta: tylko w narożnych przypadkach, w których zamiana zawartości rzuca się i nie może się bezpiecznie wycofać, kończy się w stanie pustym.
Zachowuje się trochę jak std::tuple
, a trochę jak std::optional
.
Używanie std::get
i std::get_if
jest zwykle złym pomysłem. Prawidłowa odpowiedź to zwykle std::visit
, która pozwala poradzić sobie z każdą dostępną tam możliwością. if constexpr
może być użyty w trakcie visit
jeśli potrzebujesz rozgałęzić swoje zachowanie, zamiast wykonywać sekwencję kontroli środowiska wykonawczego, która powiela to, co visit
zrobi bardziej efektywnie.
Podstawowe użycie std :: wariant
Tworzy to wariant (oznaczony związek), który może przechowywać int
lub string
.
std::variant< int, std::string > var;
Możemy przechowywać w nim jeden z tych typów:
var = "hello"s;
I możemy uzyskać dostęp do zawartości poprzez std::visit
:
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
przez przekazanie polimorficznej lambdy lub podobnego obiektu funkcji.
Jeśli jesteśmy pewni, że wiemy, jaki to typ, możemy go uzyskać:
auto str = std::get<std::string>(var);
ale to rzuci, jeśli źle to zrobimy. get_if
:
auto* str = std::get_if<std::string>(&var);
zwraca nullptr
jeśli zgadniesz źle.
Warianty nie gwarantują dynamicznej alokacji pamięci (innej niż alokacja według zawartych w nich typów). Przechowywany jest tam tylko jeden typ w wariancie, aw rzadkich przypadkach (z wyjątkami podczas przypisywania i brakiem bezpiecznego sposobu wycofania się) wariant może stać się pusty.
Warianty umożliwiają bezpieczne i wydajne przechowywanie wielu typów wartości w jednej zmiennej. Są to w zasadzie sprytne, bezpieczne dla typu union
.
Utwórz wskaźniki pseudo-metody
To jest zaawansowany przykład.
Możesz użyć wariantu do skasowania typu lekkiego.
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)
);
};
}
};
tworzy to typ, który przeciąża operator->*
Variant
po lewej stronie.
// 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)... );
};
Teraz, jeśli mamy 2 typy, każdy z metodą print
:
struct A {
void print( std::ostream& os ) const {
os << "A";
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
zauważ, że są to typy niepowiązane. Możemy:
std::variant<A,B> var = A{};
(var->*print)(std::cout);
i wyśle nam połączenie bezpośrednio do A::print(std::cout)
. Jeśli zamiast tego zainicjalizujemy var
pomocą B{}
, wysyłamy go do B::print(std::cout)
.
Jeśli stworzyliśmy nowy typ C:
struct C {};
następnie:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
nie uda się skompilować, ponieważ nie ma C.print(std::cout)
.
Rozszerzanie powyżej, pozwalają na swobodny funkcji print
S być wykrywane i stosowane ewentualnie z zastosowaniem if constexpr
w print
pseudo sposobem.
przykład na żywo obecnie korzystający z boost::variant
zamiast std::variant
.
Konstruowanie `std :: wariant`
Nie dotyczy to osób przydzielających.
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}