2017-09-08 6 views
3

foo(prints 0)のパラメータにテンプレートメソッドisA<void>がある場合は、struct Aの1つの関数fooを残そうとしています。このコードはコンパイルされ(gcc 6.1.0とclang-3.9.0で明示的に--std=c++14オプションが指定されています)、実行されます。この場合、SFINAEが正しく動作しないのはなぜですか?

しかし、それは1を出力しますが、私はそれが0を出力することを確信しています。私はどこが間違っているのだろうかと疑問に思うが、実際の質問は正しい。

C++ 14ソリューションのみをご利用ください。

#include <type_traits> 
#include <iostream> 
#include <utility> 

using std::enable_if; 
using std::declval; 
using std::true_type; 
using std::false_type; 
using std::cout; 

template<int M> 
struct ObjectX 
{ 
    template<typename C> 
    bool isA() { return false; } 
}; 

struct XX : ObjectX<23456> { 
    int af; 
}; 

template <typename ObjType> using has_dep = decltype(declval<ObjType>().template isA<void>()); 

template <typename, typename = void> 
struct has_isa : public false_type {}; 

template <typename ObjType> 
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {}; 

template<typename ObjType> 
struct A 
{ 
    template<typename T = void> 
    typename enable_if<has_isa<ObjType>::value, T>::type 
    foo() { 
    cout << "called foo #0" << "\n"; 
    } 

    template<typename T = void> 
    typename enable_if<!has_isa<ObjType>::value, T>::type 
    foo() { 
    cout << "called foo #1" << "\n"; 
    } 
}; 

int 
main() 
{ 
    A<XX> axx; 
    // XX().template isA<void>(); -- to check, that we can call it and it exists 
    axx.foo(); 
    return 0; 
} 
+0

2番目のテンプレートパラメータを指定しないと、 'has_isa'は' void'を持つものの代わりに自動的に 'has_dep'を使って特殊化を試みません。 – Albjenow

+0

@Albjenowこの方法は、関数メンバがテンプレートでない場合に機能します。コードを少し変更してみて(テンプレートCを削除してhas_depを修正してください)、それ以外のものがすべて正しいことがわかります。より限定されたスペシャライゼーションが常に制限の少ないものに勝つので、それをしようとします –

答えて

4


まず、has_dep<XX>boolです。 has_dep<XX>を試してみると、デフォルトのテンプレート引数を追加すると、これは実際にはhas_dep<XX, void>ということになります。しかし、専門はhas_dep<XX, bool>です。これは私たちが実際に探しているものと一致しません。 boolvoidと一致しません。だからhas_dep<XX>false_typeです。これに対する解決策はstd::void_tです。そのQ/Aを読んで、なぜ動作するのかを知ることをお勧めします。専門分野では、代わりにvoid_t<has_dep<ObjType>>を使用する必要があります。

template<typename T = void> 
typename enable_if<has_isa<ObjType>::value, T>::type 

SFINAEのみ置換の即時コンテキスト内で発生し、クラステンプレートパラメータは関数テンプレート置換の即時の文脈ではありません。


第二には、これは権利ではありません。ここで右のパターンは次のとおりです。

template <typename T = ObjType> // default to class template parameter 
enable_if_t<has_isa<T>>   // use the function template parameter to SFINAE 
foo() { ... } 

は、これら二つの修正を行い、意図したとおりにプログラムが動作します。

+0

void_tはC++ 17ですが、私は考えました。ありがとう、私は間違いなくそれに似た独自のラッパーを書くことができます –

2

has_isaが間違った特殊化を選択したため、あなたのsfinaeが失敗します。

has_isa<T>の使用は、デフォルトの実装か特殊化されたバージョンのいずれかでなければなりません。

あなたが定義されているように、あなたはvoidへのデフォルト引数を持っている:

// default argument ---------v 
template <typename, typename = void> 
struct has_isa : public false_type {}; 

は、次に表現has_isa<T>で、2番目のパラメータは無効にする必要があります。これは、has_isa<T, void>とほぼ同じです。

問題はこれです:テンプレート一部の順序付けがこの「オーバーロード」より専門的な検討するにもかかわらず

template <typename ObjType> 
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {}; 
//      ^--- what's that type? 

、それは選ばれません。 has_depの定義を見てください:

struct XX { 
    template<typename C> bool isA() { return false; } 
}; 

template <typename ObjType> 
using has_dep = decltype(declval<ObjType>().template isA<void>()); 

ねえ、そのタイプhas_dep<T>boolあるt.isA<void>()の戻り値の型です!

ので、専門的なバージョンは、次のようになります。

template <typename ObjType> 
struct has_isa<ObjType, has_dep<ObjType>> : public true_type {}; 
//      ^--- really, this is bool in our case 

だから、これが動作するためには、あなたはを呼び出す必要があります。これは非現実的であるとして、あなたはこのようあなたの専門分野を定義する必要があります。

template <typename ObjType> 
struct has_isa<ObjType, void_t<has_dep<ObjType>>> : public true_type {}; 

void_tがそうのように定義されている場合:このよう

template<typename...> 
using void_t = void; // beware for msvc 

我々としてvoidを送信するので、has_isa<T>は常に、専門を検討します第2のテンプレートパラメータであり、今では私たちの専門化は常に第2のパラメータとしてvoidとなる。


また、Barryが述べたように、sfinaeは直接的なコンテキストでのみ表示されるため、機能が正しく構成されていません。あなたのようにそれを書く必要があります:あなたは、単に機能をプライベートにする、テンプレートパラメータを公開したくない場合は

template<typename T = ObjType> 
typename enable_if<has_isa<T>::value, void>::type 
foo() { //     ^--- sfinae happens with T 
    cout << "called foo #0" << "\n"; 
} 

template<typename ObjType> 
struct A { 
public: 
    void foo() { 
     foo_impl(); 
    } 

private: 
    template<typename T = ObjType> 
    typename enable_if<has_isa<T>::value, void>::type 
    foo_impl() { 
     cout << "called foo #0" << "\n"; 
    } 

    template<typename T = ObjType> 
    typename enable_if<!has_isa<T>::value, void>::type 
    foo_impl() { 
     cout << "called foo #1" << "\n"; 
    } 
}; 
1

あなたの問題は、あなたが間違ったクラスを特殊ということです。

has_depにはvoidを返すようにしてください。

template <typename ObjType> using has_dep = decltype(static_cast<void>(declval<ObjType>().template isA<void>())); 

このプログラムでは2つの問題がありますので、ここで

template <typename ObjType> 
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {}; 
// It is really <bjType, void> you specialize. 
関連する問題