2010-03-10

decltypeとSFINAE

decltypeは、テンプレートのSubstitutionの際に、考慮される。

// #1 T::func()がある場合の処理
template < typename T >
auto f(T x) -> decltype( x.func() )
{
    std::cout << "has T::func()" << std::endl ;
    return x.func() ;
}

// #2 Tがfunc()を持たない場合の処理。
template < typename T >
auto f(T x) -> void
{
    std::cout << "no T::func()" << std::endl ;
}

struct Foo
{
} ;


int main()
{
    f( Foo() ) ;// no T::func()
}

このように、Fooはfunc()というメンバ関数を持たないので、#2が呼ばれる。では、持っていた場合はどうか。

struct Foo { } ;

struct bar
{
    void func() ;
} ;


int main()
{
    f( Foo() ) ;// no T::func()
    f( Bar() ) ;// Error オーバーロード解決が曖昧。
}

なぜだろうか。それは、SFINAEは、テンプレートのインスタンス化ができない場合、インスタンス化を行わないというルールだからだ。オーバーロード解決は、テンプレートのインスタンス化の後に行われる。この場合、どちらも、f(Bar)なので、呼び出しは曖昧である。

つまり、SFINAEは、オーバーロードの候補から、関数を消し去ることはできるが、優先順位をつけてはくれないのだ。しかし、ここでは、テンプレート実引数が、func()というメンバ関数を持つかどうかで、f()を呼び分けたい。どうすればいいのか。

それには、SFINAEを利用して、呼び出したくない関数を、オーバーロードの候補から外してやればいい。

template < typename T >
T && value() ;

template < typename T >
struct has_func
{
private :
    template < typename U >
    static auto check(U x) -> decltype( x.func(), std::true_type() ) ;
    static std::false_type check(...) ;

public :
    static bool const value = std::identity<decltype( check( value<T>() ) )>::type::value ;
} ;



template < typename T >
auto f(T x) -> decltype( x.func() )
{
    std::cout << "has T::func()" << std::endl ;

    return x.func() ;
}


template < typename T >
auto f(T x) -> typename std::enable_if< !has_func<T>::value >::type
{
    std::cout << "no T::func()" << std::endl ;
}


struct Foo {} ;

struct Bar
{
    void func() {}
} ;

int main()
{
    f( Foo() ) ;// no T::func()
    f( Bar() ) ;// has T::func()

}

has_funcは、Tがfunc()というメンバ関数を持つかどうかを調べるメタ関数である。このように、enable_ifに渡すことによって、メタ関数がtrueを返す場合だけ、テンプレートのsubstitutionを失敗させることができる。std::identitiyというメタ関数に渡しているのは、現在のドラフトでは、decltype(...)::typeができないためである。これは、今行われているピッツバーグ会議で、ドラフトが変更されて、できるようになる予定である。

ちなみに、valueというのは、現在提案されているライブラリで、メタプログラミングを助けるためのものである。valueの実装は、上の通りである。宣言だけあって、定義はない。なぜなら、valueは、decltypeの中で、型としての値を返すために使われることを想定しているからだ。

ちなみに、戻り値に型を書くのが嫌な場合、C++0xでは、関数テンプレートのデフォルト引数が認められているので、以下のように書くこともできる。

template < typename T, typename U = typename std::enable_if< !has_func<T>::value >::type >
auto f(T x) -> void
{
    std::cout << "no T::func()" << std::endl ;
}

1 comment:

Anonymous said...

VCのバグだと思いますが、x.func()の部分をコンストラクタやキャストをする文に変えて、falseになるような型を与えてやると、コンパイラが強制終了してしまいました。