最終更新日時:
が更新

履歴 編集

ユーザー定義型を扱える型安全な共用体

ユーザー定義型を共用体で扱うには、Boost Variant Libraryを使用する。

インデックス

基本的な使い方

まず、Boost.Variantの基本的な使い方を以下に示す:

#include <iostream>
#include <string>
#include <boost/variant.hpp>

struct var_printer : boost::static_visitor<void> {
    void operator()(int x) const
        { std::cout << x << std::endl; }

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

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

int main()
{
    // int, string, doubleのオブジェクトが格納されうる型
    boost::variant<int, std::string, double> v;

    v = 3; // int型の値を代入
    boost::apply_visitor(var_printer(), v); // visitorで型ごとの処理を行う

    v = "hello"; // 文字列を代入
    boost::apply_visitor(var_printer(), v);
}

出力:

3
hello

boost::variantクラステンプレートが、型安全に抽象化された共用体である。そのテンプレート引数として、格納されうる型を列挙する。

boost::variantクラスは、テンプレートパラメータで指定された型のオブジェクトを、コピー/ムーブコンストラクタおよび代入演算子で代入できる。

boost::apply_visitor()関数に指定する関数オブジェクトは、boost::static_visitorクラスから派生したクラスであり、boost::variantオブジェクトにどの型のオブジェクトが格納されているのかによって、関数呼び出し演算子を適切にオーバーロードしてくれる。

どの型が格納されているかを判定する

boost::variantオブジェクトにどの型が格納されているか判定するにはwhich()メンバ関数、もしくはtype()メンバ関数を使用する。

which()メンバ関数は、格納されている型の、0から始まるインデックスを返す。

#include <iostream>
#include <string>
#include <boost/variant.hpp>

int main()
{
    boost::variant<int, std::string, double> v;

    // 空の状態
    std::cout << v.which() << std::endl;

    v = 1; // int型の値を格納
    std::cout << v.which() << std::endl;

    v = 3.14; // double型の値を格納
    std::cout << v.which() << std::endl;
}

出力:

0
0
2

type()メンバ関数は、格納されている型のstd::type_infoオブジェクトへのconst左辺値参照を返す。

#include <iostream>
#include <string>
#include <boost/variant.hpp>

int main()
{
    boost::variant<int, std::string, double> v;

    v = 1; // int型の値を格納
    if (v.type() == typeid(int)) {
        std::cout << "int" << std::endl;
    }

    v = 3.14; // double型の値を格納
    if (v.type() == typeid(double)) {
        std::cout << "double" << std::endl;
    }
}

出力:

int
double

格納されている値を取り出す

boost::variantオブジェクトに格納されている値を取り出すには、boost::get()非メンバ関数を使用する。 この関数には参照版とポインタ版の2種類が用意されている。それぞれの特徴は以下の通り:

  • 参照版 : boost::get()非メンバ関数にboost::variantオブジェクトへの参照を渡すと、格納されている値への参照を返す。指定された型が格納されている型と同じではない場合、boost::bad_get型の例外を送出する。
  • ポインタ版 : boost::get()非メンバ関数にboost::variantオブジェクトへのポインタを渡すと、格納されている値へのポインタを返す。指定された型が格納されている型と同じではない場合、ヌルポインタを返す。

#include <iostream>
#include <string>
#include <boost/variant.hpp>

int main()
{
    boost::variant<int, std::string, double> v;
    v = 1; // int型の値を格納

    // 参照版
    try {
        int& x = boost::get<int>(v);
        std::cout << x << std::endl;
    }
    catch (boost::bad_get& e) {
        std::cout << e.what() << std::endl;
    }

    // ポインタ版
    if (int* x = boost::get<int>(&v)) {
        std::cout << *x << std::endl;
    }
    else {
        std::cout << "int値は格納されていない" << std::endl;
    }
}

出力:

1
1

値をクリアする

Boost.Variantには決して空にはならない保証という考え方があるため、他の値を入れることはできてもクリアはできない。clear()関数は用意されておらず、empty()メンバ関数は常にfalseを返す。

どうしてもクリアしたい場合は、boost::blankという型をvariantに格納できるように指定する。これは単なる中身が空のクラスである。

which()メンバ関数やtype()メンバ関数を使用して、boost::blankオブジェクトが格納されているかどうかで、空かどうかを判定する。

boost::variantクラスは、そのデフォルトコンストラクタで第1テンプレートパラメータのオブジェクトを構築するので、boost::blankは第1テンプレートパラメータとして指定することを推奨する。

#include <iostream>
#include <string>
#include <boost/variant.hpp>

int main()
{
    boost::variant<boost::blank, int, std::string> v = boost::blank();
    v = 3;

    v = boost::blank();

    if (v.type() == typeid(boost::blank)) {
        std::cout << "blank" << std::endl;
    }
    else {
        std::cout << "no blank" << std::endl;
    }
}

出力:

blank

variantを再帰的にする

boost::variant に含める型リストに boost::variant で定義したい型それ自身も含めたい場合には boost::variant<...>boost::make_recursive_variant<...>::type に替えて、自身の型に相当する型引数には boost::recursive_variant_ を渡す事で、boost::variant によって定義される型にそれ自身の型を再帰的に含む型を生成できる。

boost::make_recursive を用いた再帰的な boost::variant 型の定義による float double long double bool を末端の値とし、 それらの std::vectorstd::unordered_map によるコンテナーも再帰的に定義した型と、 boost::apply_visitor による JSON 風の出力を行う例を示す。

#include <boost/variant.hpp>
#include <unordered_map>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <limits>

using property_type = boost::make_recursive_variant
< bool
, float, double, long double
, std::vector< boost::recursive_variant_ >
, std::unordered_map< std::string, boost::recursive_variant_ >
>::type;

using array_type = std::vector< property_type >;
using map_type   = std::unordered_map< std::string, property_type >;

class printer
  : boost::static_visitor< void >
{
  std::ostream& _s;
  const std::size_t _nest_level = 0;

  public:

  explicit printer( std::ostream& s, const std::size_t nest_level = 0 )
    : _s( s )
    , _nest_level( nest_level )
  { }

  auto indent( const std::size_t delta_nest_level = 0 ) const
  {
    for ( std::size_t n = 0; n < _nest_level + delta_nest_level; ++ n )
      _s << "  ";
  }

  auto operator()( const array_type& o ) const -> void
  {
    indent();
    _s << "[\n";
    for ( auto i = o.cbegin(); i != o.cend(); ++i )
    {
      boost::apply_visitor( printer( _s, _nest_level + 1 ), *i );
      if ( i + 1 != o.cend() )
        _s << ",";
      _s << "\n";
    }
    indent();
    _s << "]";
  }

  auto operator()( const map_type& o ) const -> void
  {
    indent();
    _s << "{\n";
    for ( auto i = o.cbegin(); i != o.cend(); )
    {
      indent( 1 );
      _s << '"' << i->first << '"' <<  ":\n";
      boost::apply_visitor( printer( _s, _nest_level + 2 ), i->second );
      if ( ++i != o.cend() )
        _s << ",";
      _s << "\n";
    }
    indent();
    _s << "}";
  }

  auto operator()( const bool e ) const -> void { indent(); _s << std::boolalpha << e; }

  template < typename T >
  auto operator()( const T e ) const -> void { indent(); _s << std::fixed << std::setprecision( std::numeric_limits< T >::max_digits10 - 2 ) << e; }
};

auto main() -> int
{
  array_type p1;
  p1.emplace_back( 1.23e-4f );
  p1.emplace_back( 1.23e-4 );
  p1.emplace_back( 1.23e-4l );
  p1.emplace_back( true );
  p1.emplace_back( p1 );
  map_type p2;
  p2.emplace( "fuga", p1 );
  p2.emplace( "piyo", false );
  p1.emplace_back( p2 );
  property_type p = p1;

  boost::apply_visitor( printer( std::cout ), p );
}

出力例:

[
  0.0001230,
  0.000123000000000,
  0.0001230000000000000,
  true,
  [
    0.0001230,
    0.000123000000000,
    0.0001230000000000000,
    true
  ],
  {
    "piyo":
      false,
    "fuga":
      [
        0.0001230,
        0.000123000000000,
        0.0001230000000000000,
        true,
        [
          0.0001230,
          0.000123000000000,
          0.0001230000000000000,
          true
        ]
      ]
  }
]

C++の国際標準規格上の類似する機能

documentated boost version is 1.52.0