C++
std :: variant
サーチ…
備考
バリアントは、元のunion
使用に代わるものです。これは型保証され、どの型かを知っており、必要な時にその中のオブジェクトを慎重に構築し破棄します。
それはほとんど決して空ではありません:そのコンテンツを置き換えて安全に戻ってくることができないコーナーケースでのみ、それは空の状態に終わるかどうかです。
これはstd::tuple
ように動作し、 std::optional
と多少似ていstd::optional
。
std::get
とstd::get_if
は、普通は悪い考えです。正しい答えは通常はstd::visit
であり、そこではあらゆる可能性に対処できます。あなたの行動を分岐させる必要visit
場合、 visit
より効率的に行うvisit
に実行時検査のシーケンスを実行するのではなく、 if constexpr
をvisit
内で使用できるかどうかを確認します。
基本的な::バリアントの使用
これにより、 int
またはstring
格納できるバリアント(タグ付き共用体)が作成され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
返します。
バリアントは、ダイナミックメモリ割り当てを保証しません(ただし、含まれている型によって割り当てられる以外は)。バリアント内の1つの型だけがそこに格納されており、まれに(割り当て中に例外が発生し、バックアウトする安全な方法がない)、バリアントが空になることがあります。
バリアントを使用すると、複数の値タイプを1つの変数に安全かつ効率的に格納できます。彼らは基本的にスマートな、型安全な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)
);
};
}
};
これは、左側にVariant
を持つoperator->*
をオーバーロードする型を作成します。
// 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)... );
};
今では、それぞれにprint
メソッドを持つ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)
に直接呼び出しをA::print(std::cout)
ます。代わりにB{}
でvar
を初期化すると、 B::print(std::cout)
ディスパッチされます。
新しいタイプCを作成した場合:
struct C {};
次に:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
C.print(std::cout)
メソッドがないため、コンパイルに失敗します。
上記を拡張することで、フリー関数のprint
を検出して使用することができます。おそらく、 print
疑似メソッド内でif constexpr
を使用することで可能です。
ライブの例では、現在使用して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}