variantの実装(3) -placement new-
前回(variantの実装(2) -aligned_storageを作る- - while( c++ );)のつづき。
参考
アライメントの問題はおそらく解決できたので、placement newを使ってスタック上の未初期化領域に任意のオブジェクトを生成してみます。
class Hoge { public: Hoge( char a = 0, int b = 0, short c = 0 ) : a( a ), b( b ), c( c ) {} private: char a; int b; short c; }; int main() { typedef aligned_storage < sizeof( Hoge ) //12 , alignment_of< Hoge >::value //4 > storage_t; storage_t storage; Hoge* p = new( storage.address() ) Hoge( 1, 2, 3 ); p->~Hoge(); return 0; }
たぶん問題無いですよね?
variant
これでvariantに任意の型のオブジェクトを格納できるようになりました。
早速、本格的にvariantの実装をしていきましょう。ワクワク。
(このエントリを書いてる時点で、とりあえずboost::variantっぽいものは出来てるんですけどね)
実装
template< typename T1, typename T2, ..., typename TN > class variant { typedef variant this_type; typedef typelist< typename T1, typename T2, ..., typename TN > list_type; typedef typename make_storage< list_type >::type storage_type; storage_type storage_; int which_; //storage_に格納されているオブジェクトの型を表す番号1〜N public: template< typename T > variant( const T& value ) { //型リストからTに一致する型を見つけてストレージに格納する typedef find< T, list_type > found; new ( storage_.address() ) typename found::type( value ); which_ = found::value; } };
make_storage
型リストからストレージ型を作成するメタ関数。
実装
//型リストからストレージ型を作成 template< typename TypeList > struct make_storage { typedef aligned_storage < TypeList::max_size , TypeList::max_alignment > type; };
find
型リストからTに一致する型を探し、かつその番号(1〜)を取得する。見つからない場合、void_を返すメタ関数。
実装
//型リストからTを検索、かつその番号を取得。見つからない場合はvoid_を返すメタ関数。 template< typename T, typename TypeList, int N = 1, bool stop = ( N > TypeList::length ) > struct find { private: //型リスト内のTNのtype、valueを取得 typedef get_n< TypeList, N > get; //Tと一致する型を再帰的に検索 typedef typename selector < is_same< typename get::type, T >::value , get , find< T, TypeList, N + 1 > >::type found; public: typedef typename found::type type; enum{ value = found::value }; }; template< typename T, typename TypeList, int N > struct find< T, TypeList, N, true > { typedef void_ type; enum{ value = 0 }; };
get
型リストのN番目の型と番号を取得するメタ関数。
//型リストのN番目の型と番号を取得 template< typename TypeList, int N > struct get_n; // //template< typename TypeList > //struct get_n< TypeList, 1 >{ enum{ value = 1 }; typedef typename TypeList::type1 type; }; //template< typename TypeList > //struct get_n< TypeList, 2 >{ enum{ value = 2 }; typedef typename TypeList::type2 type; }; //... //template< typename TypeList > //struct get_n< TypeList, n >{ enum{ value = n }; typedef typename TypeList::typen type; }; // #define pp_get_n( n, p ) \ template< typename TypeList > \ struct get_n< TypeList, n > \ { \ enum { value = n }; \ typedef typename TypeList::type##n type; \ }; pp_repeat( max_typelist_params, pp_get_n, pp_get_n, ignore );
variant
variantのデストラクタでは、型の番号に合わせてストレージをキャストし、デストラクタを呼び出します。
template< typename T1, typename T2, ..., typename TN > class variant { ... ~variant() { //型に対応するデストラクタを呼び出す switch( which_ ) { case 1: static_cast< T1* >( storage_.address() )->~T1(); break; case 2: static_cast< T2* >( storage_.address() )->~T2(); break; ... case N: static_cast< TN* >( storage_.address() )->~TN(); break; } } };
型リストにintやfloatなどのprimitiveな型が含まれる場合、
static_cast< int* >( storage_.address() )->~int();
となり、コンパイルエラーになりそうですが、コンパイラによって無視?されるようです。
また、switch文の各caseはテンプレート引数の個数Nに依存するので、プリプロセッサを使って書き直します。
//case n: // static_cast< TN* >( storage_.address() )->~TN(); // break; #define variant_case( n, p ) \ case n: \ static_cast< pp_cat( T, n )* >( storage_.address() )->~pp_cat( T, n )(); \ break; switch( which_ ) { pp_repeat( max_typelist_params, variant_case, variant_case, ignore ); } #undef variant_case
使ってみる
#include "variant.h" int main() { //variant< int, std::string, double > v( 1 ); variant< int, std::string, double > v( std::string( "hoge" ) ); //variant< int, std::string, double > v( 3.14 ); //variant< int, std::string, double > v( 3.14f ); //コンパイルエラー return 0; }