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 );
    }
};

ここまでのサンプル

次回は、代入演算子、コピーコンストラクタなどを実装予定です。
ただ、例外安全を考えると面倒だなあ。。。
melponさんが解説しています。