最終更新日時(UTC):
が更新

履歴 編集

class template
<variant>

std::variant

namespace std {
  template <class... Types>
  class variant;
}

概要

variantクラスは、格納されうる候補の型リスト (Types...) に含まれる型のオブジェクトを切り替えながら保持する記憶域型である。継承関係にない複数の型を、単一のオブジェクトに代入・切り替えができる。

これは、継承によって基底クラスのオブジェクトに派生クラスのオブジェクトを代入できることと同様に、候補として列挙した複数の型のオブジェクトを単一の型のオブジェクトに代入して扱えるという点で、多態性を表現している。インタフェースが異なる複数の型の場合はビジター関数オブジェクトによって型ごとの動作を定義でき、インタフェースが共通の型の場合は、関数テンプレートの関数呼び出し演算子をもつ関数オブジェクトをビジターにすることで共通の動作をさせることができる。

// 継承関係にないクラス群
struct A { void f() {} };
struct B { void f() {} };
struct C { void f() {} };

// A, B, Cのいずれかの型を代入できる型
std::variant<A, B, C> v = A{}; // A型のオブジェクトを代入
v = B{}; // B型のオブジェクトに切り替え

// B型オブジェクトを保持しているか
if (std::holds_alternative<B>(v)) {
  // 保持しているB型オブジェクトを取得
  B& b = std::get<B>(v);
}

// どの型が代入されていたとしても、共通のインタフェースを呼び出す
std::visit([](auto& x) {
  x.f();
}, v);

このクラスと同様のことは共用体を使用しても達成できるが、このクラスはより使いやすいよう設計されている。

このクラスは追加の動的メモリ確保は行わず、保持するオブジェクトを自身のオブジェクト表現内に直接割り当てる。

備考

  • このクラスはBoost Variant Libraryを元に設計されている
  • Boost Variant Libraryは、recursive variantによって再帰的なデータ構造を扱えるが、現時点のstd::variantクラスではそのようなデータ構造は扱えない
    • これは、JSONデータ形式のように値として数値・文字列・配列などを設定でき、配列の要素にもまた数値・文字列・配列などを設定できる、というようなデータの読み込み、書き込みで必要となる
  • Boost Variant Libraryは、「決して空にならない保証 ("Never-Empty" Guarantee)」を提供しており、たとえ代入中に例外が発生したとしても、候補型のいずれの型も代入されていない状況が起こらないよう設計・実装されていた。標準ライブラリに導入されたこのクラスは、代入中に例外が発生した場合に空になる可能性をもっている
  • このクラスは、他の言語で「代数データ型 (Algebraic data type)」「直和型 (Union type, Sum type)」「タグ付き共用体 (Tagged union)」と呼ばれる機能の一部を表現できる。また、Either型として近しい機能が提供されている場合もある

テンプレートパラメータ制約

  • Types...の全ての型が、std::destructible要件を満たすこと
    • 例えばvoidはこれを満たさない。空、あるいは無効な型を入れ込みたい場合にはstd::monostateが使用できる
  • コンストラクタや代入の制約として、variant<std::string, std::string>のように、Types...内に同じ型が複数回現れる指定をする場合は、型のインデックスを指定する形式の機能のみ使用できる
    • こういった指定は、正常データかエラーデータどちらかが代入されるオブジェクトを用意する状況で、正常データとエラーデータがどちらも文字列、という場合に必要になる

適格要件

  • Types...が空ではないこと

メンバ関数

構築・破棄

名前 説明 対応バージョン
(constructor) コンストラクタ C++17
(destructor) デストラクタ C++17

代入

名前 説明 対応バージョン
operator= 代入演算子 C++17
emplace 要素型のコンストラクタ引数から直接構築する C++17
swap 他のvariantオブジェクトとデータを入れ替える C++17

値の観測

名前 説明 対応バージョン
valueless_by_exception 例外によって値を持たない状態になっているかを判定する C++17
index 候補型の何番目の型が代入されているかを取得する C++17

非メンバ関数

値の取得

名前 説明 対応バージョン
get 保持している値を取得する C++17
get_if 保持している値を指すポインタを取得する。エラー時にヌルポインタを返す C++17

ビジター

名前 説明 対応バージョン
visit variantオブジェクトが現在保持している型に対応する関数を呼び出す C++17

値の入れ替え

名前 説明 対応バージョン
swap 2つのvariantオブジェクトを入れ替える C++17

比較演算子

名前 説明 対応バージョン
operator== 等値比較 C++17
operator!= 非等値比較 C++17
operator<=> 三方比較 C++20
operator< 左辺が右辺より小さいかを判定する C++17
operator<= 左辺が右辺以下かを判定する C++17
operator> 左辺が右辺より大きいかを判定する C++17
operator>= 左辺が右辺以上かを判定する C++17

ハッシュサポート

名前 説明 対応バージョン
template <class T> struct hash; hashクラスの先行宣言 C++17
template <class ...Types> struct hash<variant<Types...>>; hashクラスのvariantに対する特殊化 C++17

アロケータインタフェース

名前 説明 対応バージョン
template <class T, class Alloc> struct uses_allocator; uses_allocatorクラスの先行宣言 C++17
template <class... Types, class Alloc>
struct uses_allocator<variant<Types...>, Alloc>;
uses_allocatorクラスのvariantに対する特殊化 C++17

#include <iostream>
#include <variant>
#include <string>

int main()
{
  // int, char, std::stringのいずれかの型の値を保持できる型
  std::variant<int, char, std::string> v = 3; // int型の値を代入

  // 候補型の0番目の型 (int) を保持しているか
  if (v.index() == 0) {
    int& x = std::get<0>(v); // 型のインデックスを指定して、保持している値を取得
    std::cout << x << std::endl;
  }

  v = std::string("Hello"); // std::string型オブジェクトを代入

  // std::string型を保持しているか
  if (std::holds_alternative<std::string>(v)) {
    std::string& x = std::get<std::string>(v); // 型を指定して、保持している値を取得
    std::cout << x << std::endl;
  }
}

出力

3
Hello

バージョン

言語

  • C++17

処理系

参照