variantの実装(4) -static_visitor-
前回(variantの実装(3) -placement new- - while( c++ );)のつづき。
variantに任意のオブジェクトを格納することが出来たので、今回はオブジェクトを取り出してみます。
variantに格納されているのオブジェクト(storage_)の型は、メンバwhich_で型に対応する番号として管理されています。
template< typename T1, typename T2, ..., typename TN > class variant { storage_type storage_; int which_; //T1 -> 1 //T2 -> 2 // ... //TN -> N };
ということは、前回のデストラクタのようにwhich_の値でキャストすればよさそうですね。
template< typename T1, typename T2, ..., typename TN > class variant { int which_; //T1 -> 1 //T2 -> 2 // ... //TN -> N T& get()const { switch( which_ ) { case 1: return *static_cast< T1* >( storage_.address() ); break; case 2: return *static_cast< T2* >( storage_.address() ); break; ... case N: return *static_cast< TN* >( storage_.address() ); break; } return ???; } };
しかし、上記の例ではコンパイルエラーになってしまいます。戻り値の型とstorage_の型が一致しないのでreturn出来ません。
static_visitor
boostではstatic_visitorのオーバーロードによって上記の問題を解決しています。
template< typename T = void > struct static_visitor { typedef T result_type; protected: static_visitor(){} ~static_visitor(){} }; template< typename T > struct visitor : static_visitor< T* > { //variantのwhich_とTが一致する場合、storage_のアドレスを返す result_type operator()( T& storage )const { return &storage; } //variantのwhich_とTが一致しない場合、nullを返す template< typename U > result_type operator()( U& )const { return 0; } };
template< typename T1, typename T2, ..., typename TN > class variant { //storage_に格納されている値を取得 template< typename T > T& get()const { visitor< T > v; T* p = accept( v ); //storage_の型とTが一致しない if( p == 0 )throw bad_cast(); return *p; } //visitorの受け入れ template< typename Visitor > typename Visitor::result_type accept( Visitor& v )const { switch( which_ ) { case 1: return internal_visit< T1 >( v ); break; case 2: return internal_visit< T2 >( v ); break; ... case N: return internal_visit< TN >( v ); break; } return internal_visit< void_ >( v ); } template< typename T, typename Visitor > typename Visitor::result_type internal_visit( Visitor& v )const { return v( *static_cast< T* >( storage_.address() ) ); } };
使ってみる
#include "variant.h" int main() { variant< int, std::string, double > v( std::string( "hoge" ) ); std::cout << v.get< std::string >() << std::endl; //hoge std::cout << v.get< int >() << std::endl; //例外がthrowされる return 0; }
destroy_visitor
variantのデストラクタ内の処理もstatic_visitorで書き換えてみましょう。
storageに格納されているのオブジェクトのデストラクタを呼び出すdestroy_visitorを作ります。
struct destroy_visitor : static_visitor<> { template< typename T > void operator()( T& operand ) { operand; //warningの回避 operand.~T(); } };
template< typename T1, typename T2, ..., typename TN > class variant { ~variant() { destroy(); } //storage_に格納されているオブジェクトのデストラクタを呼び出す void destroy() { destroy_visitor visitor; accept( visitor ); } };
ここまでのサンプル
- variant_03.zip
- VC++2008EEで動作確認
次回は、代入演算子、コピーコンストラクタなどを実装予定です。
ただ、例外安全を考えると面倒だなあ。。。
melponさんが解説しています。