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マクロを用意します。
型リストの要素数
空の型を表す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