はじめに
編集variant
ヘッダーでは、std::variant
型が定義されています。
std::variant
型は、異なる型の値を一つずつ保持できる型安全な多様体(discriminated union)です。C++に標準で用意されていたunion型は、型安全性が欠けていましたが、std::variant
型はその欠点を解消しています。
monostate
は、空の状態を表す特別な型で、variant
の代替型として使われます。
variantクラステンプレート
編集コンストラクタ
編集- デフォルトコンストラクタで空の状態で初期化できます
- コピー・ムーブコンストラクタがあります
T&&
値から直接構築できるコンストラクタがありますin_place_type_t
、in_place_index_t
を使った直接生成もできます
デストラクタ
編集デストラクタで、variant
が保持していた値が適切に破棄されます。
代入演算子
編集コピー代入とムーブ代入、およびT&&
値による直接代入ができます。
emplace関数
編集emplace
を使うと、イン場合構築でvariant
に値を設定できます。型インデックスか型を直接指定します。
状態観測関数
編集valueless_by_exception
で空の状態かどうかを判定でき、index
で現在のインデックス(型)を取得できます。
スワップ
編集swap
関数で、2つのvariant
オブジェクトを入れ替えられます。
補助クラステンプレート・別名テンプレート
編集variant_size
、variant_size_v
は、variant
の代替型の数を取得するトレイトです。variant_alternative
、variant_alternative_t
は、インデックスから代替型を取得します。
値アクセス関数
編集holds_alternative
編集holds_alternative
は、variant
が指定した型の値を保持しているかを判定します。
get
編集get
は、インデックスか型を指定して、variant
内の値へアクセスします。型が一意でない場合はエラーになります。
get_if
編集get_if
は、get
に似ていますが、アクセスに失敗した場合はヌルポインタを返します。
比較演算子
編集variant
には、==
!=
<
>
<=
>=
<=>
などの比較演算子が多数定義されています。内部の値同士を比較し、型が異なる場合は型ごとに総序定義されています。
monostate
同士の比較にも、==
と<=>
が定義されています。
visitation
編集visit
関数は、variant
の内部の値に基づいて、指定した関数オブジェクトまたはラムダを呼び出す手段を提供します。これにより、union風の分岐処理が型安全に書けます。
例外クラス
編集bad_variant_access
は、variant
への不正なアクセスが行われた場合にスローされる例外クラスです。
補助機能
編集std::swap
にvariant
の特殊化があり、効率的なスワップが可能です。std::hash
にもvariant
用の特殊化が用意されています。
使用例
編集variant
は、従来のunionの代替として利用できます。ただし、型安全性が高く、メタデータも扱えるので、unionよりはるかに安全で生産的です。
visitation
を使えば、visitorパターンを安全に実装でき、実行時の型に基づいて処理を分岐できます。
// unionの代替としてのvariant union Value { int int_val; double double_val; std::string str_val; }; // 上のunionを安全に扱うのは難しい // variantを使った例 std::variant<int, double, std::string> value; value = 42; // int value = 3.14; // double value = "hello"s; // std::string // 型安全にアクセス可能 if (std::holds_alternative<int>(value)) { std::cout << "int: " << std::get<int>(value) << std::endl; } else if (std::holds_alternative<double>(value)) { std::cout << "double: " << std::get<double>(value) << std::endl; } else if (std::holds_alternative<std::string>(value)) { std::cout << "string: " << std::get<std::string>(value) << std::endl; }
この例では、variant
を使うことで、intとdoubleとstd::stringの値を安全に保持できています。アクセスする際もget
関数で型を指定するので、型安全性が保たれます。
次に、visitation
の使用例を示します。
std::variant<int, double, std::string> get_value() { // ...値を取得する処理... return /* ... */; } void handle_value(const std::variant<int, double, std::string>& value) { std::visit([](const auto& val) { using T = std::decay_t<decltype(val)>; if constexpr (std::is_same_v<T, int>) { std::cout << "int: " << val << std::endl; } else if constexpr (std::is_same_v<T, double>) { std::cout << "double: " << val << std::endl; } else if constexpr (std::is_same_v<T, std::string>) { std::cout << "string: " << val << std::endl; } }, value); } // ... auto value = get_value(); handle_value(value);
この例では、get_value
関数がvariant
を返し、handle_value
関数がそのvariant
の値に応じた処理をstd::visit
で行っています。ラムダ式の中では、テンプレート引数の型T
からvariant
の代替型を判別し、型に応じた処理を行っています。
visitation
を使えば、実行時の型に基づく分岐処理を、unionのようにたくさんのtypeid
チェックを書く必要なく、型安全に記述できます。
std::any
との違いstd::variant
とstd::any
はどちらも実行時の型の値を扱うための標準ライブラリの型ですが、いくつか大きな違いがあります。
std::variant
- 保持できる型をコンパイル時に列挙する必要がある
- 格納されている値の型がコンパイル時に分かる(つまり、型安全)
- 適切な代替型に基づいた効率的な実装が可能
- 代替型に対するメタデータ(インデックスなど)にアクセスできる
- visitation機能により、実行時の型に基づいた処理が可能
std::any
- 実行時に任意の型の値を格納できる
- 格納されている値の型は実行時でしか分からない(つまり、型安全ではない)
- 内部実装は型エラーズされているため、非効率
- メタデータへのアクセスはない
- visitation機能がない
つまり、std::variant
は静的に型が決まっており、それに応じた最適化や機能が提供される一方、std::any
は実行時に任意の型を扱えるがそれに伴う制約があります。
std::variant
を使う場合は、保持する型を事前に列挙できるような状況で、型安全性や最適化が必要とされます。一方、std::any
はその制約がなく、柔軟に任意の型を扱う必要がある場合に使われます。
std::variant
の方が効率的で型安全なコードが書けますが、std::any
の方が柔軟性は高くなります。用途に応じて適切な型を選択する必要があります。variantの実装
編集variant
は内部で、格納している値の型に対応するインデックスと、その値へのポインタ(またはその値自体の広げ開いた表現)を保持しています。
まとめ
編集std::variant
型は、型安全な多様体としてunionを置き換えるものです。variantは、ペイロードの型に応じた多数の演算、visitation、高い型安全性など、unionにはない様々な機能を持っています。実行時の値の型に応じた処理が必要な場面で、variantは強力なツールとなります。