読者です 読者をやめる 読者になる 読者になる

C++1z 型安全な共用体variantクラス

C++

C++1zから、型安全な共用体(type-safe union)の実装であるvariantクラスが導入されます。

このクラスは、テンプレート引数で与えた候補型のリストに含まれる型のオブジェクトを代入できる型です。また、ビジター関数オブジェクトを使用することにより、現在代入されている型のオブジェクトを、安全に操作できます。

variantクラスとその関連操作のために、<variant>ヘッダが新設されます。

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

struct visitor {
    void operator()(int x)
    { std::cout << "int : " << x << std::endl; }

    void operator()(const std::string& x)
    { std::cout << "std::string : " << x << std::endl; }

    void operator()(double x)
    { std::cout << "double : " << x << std::endl; }
};

int main()
{
    // vには、int, std::string, doubleのいずれかが代入される。
    // デフォルト構築では、0番目の型が値初期化される。
    // ここではint()の値を持つ
    std::variant<int, std::string, double> v;

    v = 3;       // int値を代入
    v = "hello"; // 文字列を代入
    v = 1.23;    // double値を代入

    // どの型が代入されているかに関わらず、共通の操作を適用する
    std::visit([](auto x) { std::cout << x << std::endl; }, v);

    // 代入されている型ごとに異なる処理を適用する
    std::visit(visitor(), v);

    // 代入されている値を取り出す
    // 指定した型の値が入っていなかったら例外が送出される
    try {
        double d = std::get<double>(v);
    }
    catch (std::bad_variant_access& e) {
        std::cout << e.what() << std::endl;
    }

    // 代入されている値を取り出す
    // 指定した型の値が入っていなかったらヌルポインタが返る
    if (double* p = std::get_if<double>(&v)) {
    }
    else {
        std::cout << "doesn't contains double value" << std::endl;
    }
}

出力:

1.23
double : 1.23

std::variantのデフォルトコンストラクタは、第1テンプレート引数に指定された型を値初期化します。variantを空にしたい場合には、std::monostateという空のクラスをvariantの第1テンプレート引数に指定することになります。

そのほかに、variantが不正な状態として空になる場合があります。これは、以下のように代入する型を変更する状況で、処理の内部で例外が発生した場合です:

struct S { operator int() { throw 42; }};
variant<float, int> v{12.f};
v.emplace<1>(S());

ここではemplace()メンバ関数の中で例外が発生しますが、その際はvariantオブジェクトは不正な状態となります。このような状態では、valueless_by_exception()という述語メンバ関数trueを返し、現在代入されている型のインデックスを取得するindex()std::variant_npos値を返すようになります。

Boostと標準の差異

Boost 1.61.0時点のvariantと標準に予定されているものには、以下のような差異があります。

  • Boostのvariantには「決して空にならない保証 (Never empty guarantee)」がある。そのために、標準とは違い不正な状態というものにはならない
  • Boostのvariantはビジターを適用するためにapply_visitor()メンバ関数を持つ。標準ではvisit()メンバ関数
  • Boostのvariantは代入されている型のインデックスを取得するwhich()メンバ関数を持つ。標準ではindex()メンバ関数
  • Boostのvariantには再帰的定義のためのrecursive_variantがあるが、標準にはない

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。