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}