variantの実装(1) -みんな大好きテンプレートメタプログラミング!-

multi-type, single value.

1つの変数で複数の型を扱いたい場合、C言語ではunionを使用しますが、
C++ unionでは、std::stringを扱うことが出来ません。

union
{
    int a;
    double b;
    std::string c;   //illegal: std::string is not a POD type!
};

C++でunion的なものを使いたい場合、Boost.Variantが便利です。

boost::variant< int, double, std::string > v;
v = 1;
v = 2.0;
v = "hoge";

型リストの中で最大サイズの型(上記の例ではstd::stringの32バイト)と同じサイズのstorageが作られ、placement newによってオブジェクトが構築されます。

今回は以上のような型リストに対応する値を格納できるvariantクラスを作ってみます。
ただし、boostの実装は今の私のレベルではハードルが高すぎるので、TTL(Tiny Template Library)をベースに実装してみます。

まずはstorageを作るための準備として、いくつかのメタ関数とpreprocessorマクロを用意します。

  • typelist_traits
    • lengtht…型リストの要素数
    • largest_type…型リストの最大サイズの型
  • pp_cat…トークンの連結
  • pp_inc…preprocessorによる定数のインクリメント
  • pp_dec…preprocessorによる定数のデクリメント
  • pp_repeat…指定マクロの繰り返し
    • pp_typename_T_def…typename T1 = def, typename T2 = def, ..., typenmae Tn = def
    • など

型リストの要素数

空の型を表すvoid_以外の型の個数(型リスト内の最も右にあるvoid_以外の型までの要素数)を求めるメタ関数。
型リストの要素を再帰的に左シフトさせて、全ての要素をvoid_にします。
全ての要素がvoid_になると、特殊化によって再帰が停止します。

struct void_{};  //empty_type

template< typename T1 = void_, typename T2 = void_, typename T3 = void_ typename T4 = void_ >
struct typelist_traits
{
    //型リストを再帰的に左シフトすることで、いずれ全てvoid_になる
    //     T2,    T3,    T4, void_
    //     T3,    T4, void_, void_
    //     T4, void_, void_, void_
    //  void_, void_, void_, void_
    typedef typelist_traits< T2, T3, T4, void_ > list;

    //型リストの要素数
    //  typelist_traits< int >::length == 1
    //  typelist_traits< int, double, std::string >::length == 3
    enum{ length = 1 + list::length };
    //  length = 1 + typelist_traits< T2, T3, T4, void_ >::length
    //  length = 1 + ( 1 + typelist_traits< T3, T4, void_, void_ >::length )
    //  length = 1 + ( 1 + ( 1 + typelist_traits< T4, void_, void_, void_ >::length ) )
    //  length = 1 + ( 1 + ( 1 + ( 1 + typelist_traits< void_, void_, void_, void_ >::length ) ) )
    //  length = 1 + ( 1 + ( 1 + ( 1 + 0 ) ) )
};

//全ての要素がvoid_の時の特殊化
template<>
struct typelist_traits< void_, void_, void_, void_ >
{
    typedef void_ list;

    enum{ length = 0 };
};

上記の例では4つのテンプレート引数しか渡せないですが、preprocessorを使って任意の個数を渡すことが可能です。
事前にBoost.Preprocessorで遊んでおくと読みやすくなると思います。
また、C++0xのVariadic Templatesを使うともっと簡単に記述できるかもしれません。

struct void_{}; //empty_type

#define max_typelist_params 10

template< pp_typename_T_def( max_typelist_params, void_ ) >
//template< typename T1 = void_, typename T2 = void_, ..., typename T10 = void_ >
struct typelist_traits
{
    //型リストを再帰的に左にシフトすることで、いずれ全てvoid_になる
    typedef typelist_traits< pp_args_lshift_T( pp_dec( max_typelist_params ) ) > list;
    //typedef typelist_traits< T2, T3, ..., T9, T10, void_ ) > list;
    //typedef typelist_traits< T3, T4, ..., T10, void_, void_ ) > list;
    //...
    //typedef typelist_traits< T10, void_, ..., void_, void_, void_ ) > list;
    //typedef typelist_traits< void_, void_, ..., void_, void_, void_ ) > list;

    //型リストの要素数を求める
    //  typelist_traits< int >::length == 1
    //  typelist_traits< int, double, std::string >::length == 3
    enum{ length = 1 + list::length };
};

//全ての要素がvoid_の時の特殊化
template<>
struct typelist_traits< pp_repeat_t( max_typelist_params, void_ ) >
//struct typelist_traits< void_, void_, ..., void_ >
{
    typedef void_ list;

    enum{ length = 0 };
};
pp_typename_T_def
//繰り返し「typename T1 = p, typename T2 = p, ..., typename Tn = p」
#define pp_typename_T_def_n( n, p ) typename T##n = p, 
#define pp_typename_T_def_n_end( n, p ) typename T##n = p

#define pp_typename_T_def( n, p ) pp_repeat( n, pp_typename_T_def_n, pp_typename_T_def_n_end, p )
pp_args_lshift_T
//「T1, T2, ..., Tn」
//    ↓左シフト
//「T2, T3, ..., Tn+1」
#define pp_args_lshift_t_n( n, t ) pp_cat( t, pp_inc( n ) ),
#define pp_args_lshift_t_n_end( n, t ) pp_cat( t, pp_inc( n ) )

#define pp_args_lshift_T( n ) pp_repeat( n, pp_args_lshift_t_n, pp_args_lshift_t_n_end, T )
pp_repeat_t
//繰り返し「t, t, ..., t」
#define pp_repeat_t_n( n, t ) t,
#define pp_repeat_t_n_end( n, t ) t

#define pp_repeat_t( n, t ) pp_repeat( n, pp_repeat_t_n, pp_repeat_t_n_end, t )
pp_repeat
#define pp_repeat_0(m,p)
#define pp_repeat_1(m,p) pp_repeat_0(m,p) m(1,p)
#define pp_repeat_2(m,p) pp_repeat_1(m,p) m(2,p)
...
#define pp_repeat_255(m,p) pp_repeat_254(m,p) m(255,p)
#define pp_repeat_256(m,p) pp_repeat_255(m,p) m(256,p)

#define pp_last_repeat_0(m,p)
#define pp_last_repeat_1(m,p) m(1,p)
#define pp_last_repeat_2(m,p) m(2,p)
...
#define pp_last_repeat_255(m,p) m(255,p)
#define pp_last_repeat_256(m,p) m(256,p)

//m(1, p)〜m(n-1, p)の繰り返しとl(n, p)
#define pp_repeat(n, m, l, p) pp_cat(pp_repeat_, pp_dec(n))(m,p) pp_cat(pp_last_repeat_,n)(l,p)
pp_inc
//increment
//pp_inc(10) -> 11
#define pp_inc( n ) pp_inc_n( n )
#define pp_inc_n( n ) pp_cat( pp_inc_, n )

#define pp_inc_0 1
#define pp_inc_1 2
...
#define pp_inc_254 255
#define pp_inc_255 256
pp_dec
//decrement
//pp_dec(10) -> 9
#define pp_dec( n ) pp_dec_n( n )
#define pp_dec_n( n ) pp_cat( pp_dec_, n )

#define pp_dec_1 0
#define pp_dec_2 1
...
#define pp_dec_255 254
#define pp_dec_256 255
pp_cat
//トークン連結
#define pp_cat( a, b ) pp_cat_( a, b )
#define pp_cat_( a, b ) a##b

最大サイズの型

型リスト内の最大サイズの型を調べるためのメタ関数。

  • selector
    • type…bool値による型の選択
  • storage
  • make_storage
    • type…型リスト内の最大サイズの型と同じサイズのstorageを作る
  • variant
bool値で型の選択
//型選択
//typedef selector< true, int, float >::type type1;    //int
//typedef selector< false, int, float >::type type2;   //float
//type1 a; //int a;
//type2 b; //float b;
template< bool condition, typename T1, typename T2 >
struct selector
{
    typedef T1 type;
};

//bool condition = false時の特殊化
template< typename T1, typename T2 >
struct selector< false, T1, T2 >
{
    typedef T2 type;
};
最大サイズの型
template< pp_typename_T_def( max_typelist_params, void_ ) >
struct typelist_traits
{
    //型リストを再帰的に左にシフトすることで、いずれ全てvoid_になる
    typedef typelist_traits< pp_args_lshift_T( pp_dec( max_typelist_params ) ) > list;

    //型リストの要素数
    enum{ length = 1 + list::length };

    //最大サイズの型
    typedef typename selector
        < sizeof( T1 ) >= sizeof( list::largest_type )
        , T1
        , typename list::largest_type
        >::type largest_type;
};

//全ての要素がvoid_の時の特殊化
template<>
struct typelist_traits< pp_repeat_t( max_typelist_params, void_ ) >
{
    typedef void_ list;

    enum{ length = 0 };

    typedef void_ largest_type;
};

最大サイズの型に合わせたstorage

//variant用ストレージ
template< int N >
struct storage
{
    char buffer[ N ];
};

//型リストからストレージを作成
template< typename TypeList >
struct make_storage
{
    typedef storage< sizeof( typename TypeList::largest_type ) > type;
};

variant

//ストレージ持っているだけのvariant
template< pp_typename_T_def( max_typelist_params, void_ ) >
struct variant
{
    typedef variant this_type;
    typedef typelist< pp_args_T( max_typelist_params ) > list;
    typedef typename make_storage< list >::type storage_t;

    storage_t storage_;
};

使ってみる

型リスト内の最大サイズの型と同じサイズのストレージが用意されたことを確認します。

int main()
{
    variant< std::string, int, double > v;
    std::cout << sizeof( v ) << std::endl;

    return 0;
}
実行結果
32

サンプル

次回はplacement newを使って実際に値を格納します。