LoginSignup
19
13

More than 5 years have passed since last update.

decltypeとSFINAEによるオーバーロード条件分岐

Last updated at Posted at 2015-12-29

decltypeとSFINAEによるオーバーロード条件分岐

この記事は「プロ生ちゃん Advent Calendar 2015 19日目」の補足記事です

decltypeはTemplate Substitutionの際に考慮されます
が!
優先順位はつけることができません
本の虫: decltypeとSFINAE
を読めばいいのですが、この記事のコードは記事の最後に書いてある通り
std::identityが削除され、めでたく
decltype(...)::typeができるようになっているため
最新のC++では動きません

江添氏に確認をとったところGFDLライセンスということなので
GFDLライセンスのもとで、記事を改変して公開します

以下の様なdecltypeを使ったSFINAEを紹介しましたが
これが、SFINAEたる所以は関数の第2引数です
value_typekey_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;
}


[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

ちなみに、いなむ先生は
関数テンプレートのデフォルト引数でSFINAEの条件を記述するのがお好みです


All text is available under the terms of the GNU Free Documentation License.

19
13
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13