#decltypeとSFINAEによるオーバーロード条件分岐
この記事は「プロ生ちゃん Advent Calendar 2015 19日目」の補足記事です
decltypeは__Template Substitutionの際に考慮されます__
が!
優先順位はつけることができません
本の虫: decltypeとSFINAE
を読めばいいのですが、この記事のコードは記事の最後に書いてある通り
std::identity
が削除され、めでたく
decltype(...)::type
ができるようになっているため
最新のC++では動きません
江添氏に確認をとったところGFDLライセンスということなので
GFDLライセンスのもとで、記事を改変して公開します
以下の様なdecltypeを使ったSFINAEを紹介しましたが
これが、SFINAEたる所以は関数の第2引数です
value_type
とkey_type
を両方持ち合わせているコンテナはないため
どちらかが__Substitution Failure__になります
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
template < typename Container >
auto my_find(Container& c, typename Container::key_type key)
->decltype((c.find(key)))
{
std::cout << "member" << std::endl;
return (c.find(key));
}
template < typename Container >
decltype(auto) my_find(Container& c, typename Container::value_type v)
{
std::cout << "free" << std::endl;
return (std::find(c.begin(),c.end(), v)) ;
}
int main(){
std::vector<int> a{1,2,3};
std::map<int,int> b;
b[0] = 1;
b[2] = 3;
b[4] = 5;
auto&& iter1 = my_find(a,2);
std::cout << ( iter1 == a.end() ? "not found" : "found" ) << std::endl;
std::cout << std::endl;
auto&& iter2 = my_find(b,2);
std::cout << ( iter2 == b.end() ? "not found" : "found" ) << std::endl;
return 0;
}
##クラスがメンバ関数void f()
を持つかどうかでオーバーロードを条件分岐させたい場合
template < typename T >
auto f(T&& arg)->decltype(arg.f())
{
std::cout << "Has f()." << std::endl;
}
template < typename T >
auto f(T&& arg)->void
{
std::cout << "Hasn't f()." << std::endl;
}
のようにした場合はどうなるかというと
まず、void f()
を持たないクラスが渡された場合
struct hoge{};
f( hoge{} ) ; // OK! Hasn't f()
上の関数がSubstitution に失敗して下の関数が候補に残り、そして呼びだされます
次に、void f()
を持っているクラスの場合
struct piyo{
void f(){}
};
f( piyo{} ) ; // オーバーロードの解決が曖昧!
decltype(arg.f())
は推論できるのでSubstitutionに成功します
両方の関数が候補になりオーバーロード解決が曖昧になった結果、コンパイルエラーになります
この事態を解決するには引数のクラスがvoid f()
を持っていた場合に
SFINAEを利用して下の関数のSubstitutionを意図的に失敗させなければなりません
そこで、メタ関数を噛ませてSFINAEで条件分岐を記述します
extern std::nullptr_t enabler;
template < typename T >
struct has_func
{
private :
template < typename U >
static auto check(U x) -> decltype( x.f(), std::true_type{} ) ;
static std::false_type check(...) ;
public :
static bool const value = decltype( check( std::declval<T>() ) )::value ;
} ;
template < typename T >
auto f( T&& arg )->decltype(arg.f())
{
std::cout << "The class has f()." << std::endl;
}
template < typename T, std::enable_if_t<!has_func<T>::value>*& = enabler >
auto f( T&& )->void
{
std::cout << "The class hasn't f()." << std::endl;
}
ちなみに、いなむ先生は
関数テンプレートのデフォルト引数でSFINAEの条件を記述するのがお好みです
All text is available under the terms of the GNU Free Documentation License.