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