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