C++
станд :: вариант
Поиск…
замечания
Вариант является заменой для использования в сыром union
. Он безопасен по типу и знает, какой он тип, и он тщательно конструирует и уничтожает объекты внутри него, когда это необходимо.
Он почти никогда не бывает пустым: только в угловых случаях, когда замена его содержимого бросается, и он не может безопасно отступить, в конечном итоге он находится в пустом состоянии.
Он ведет себя как std::tuple
и несколько похож на std::optional
.
Использование std::get
и std::get_if
- обычно плохая идея. Правильный ответ, как правило, std::visit
, который позволяет вам иметь дело с любой возможностью прямо там. if constexpr
можно использовать в ходе visit
если вам нужно разветвить ваше поведение, вместо того, чтобы выполнять последовательность проверок времени выполнения, которые дублируют то, что visit
будет делать более эффективно.
Основное использование std :: variant
Это создает вариант (тегированный союз), который может хранить либо int
либо string
.
std::variant< int, std::string > var;
Мы можем сохранить в нем один из них:
var = "hello"s;
И мы можем получить доступ к содержимому через std::visit
:
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
путем прохождения в полиморфном лямбда или подобном функциональном объекте.
Если мы уверены, что знаем, что это такое, мы можем это получить:
auto str = std::get<std::string>(var);
но это будет бросать, если мы ошибаемся. get_if
:
auto* str = std::get_if<std::string>(&var);
возвращает nullptr
если вы ошибаетесь.
Варианты не гарантируют динамического распределения памяти (кроме того, что выделяется их содержащимися типами). Там хранится только один из типов в варианте, и в редких случаях (с исключениями при назначении и без безопасного пути для возврата) вариант может стать пустым.
Варианты позволяют хранить несколько типов значений в одной переменной безопасно и эффективно. Они в основном умные, безопасные по типу union
.
Создание указателей псевдо-методов
Это расширенный пример.
Вы можете использовать вариант для стирания легкого веса.
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)
);
};
}
};
это создает тип, который перегружает operator->*
с помощью Variant
с левой стороны.
// 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)... );
};
Теперь, если у нас есть 2 типа с методом print
:
struct A {
void print( std::ostream& os ) const {
os << "A";
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
обратите внимание, что они не связаны между собой. Мы можем:
std::variant<A,B> var = A{};
(var->*print)(std::cout);
и он отправит вызов непосредственно на A::print(std::cout)
для нас. Если мы вместо этого инициализировали var
с B{}
, он отправил бы B::print(std::cout)
.
Если мы создали новый тип C:
struct C {};
затем:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
не будет компилироваться, потому что не существует C.print(std::cout)
.
Расширение вышеизложенного позволит обнаружить и использовать функцию бесплатной print
s, возможно, с использованием if constexpr
в псевдо-методе print
.
живой пример в настоящее время использует boost::variant
вместо std::variant
.
Построение `std :: variant`
Это не распространяется на распределители.
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}