2017-09-08 2 views
3

Ich versuche in struct A eine Funktion foo (Drucke 0) zu verlassen, wenn seine Parameter Vorlage Methode isA<void> und eine andere (Drucke 1) wenn nicht haben. Dieser Code (unten auf das minimale Beispiel reduziert) kompiliert (wird mit gcc 6.1.0 und clang-3.9.0 mit expliziter --std=c++14 Option versucht) und läuft.Warum funktioniert SFINAE in diesem Fall falsch und wie kann ich es beheben?

Aber es druckt 1, obwohl, ich bin mir sicher, dass es 0 drucken soll. Ich frage mich, wo ich falsch liege, aber echte Frage ist: Wie man diese Arbeit richtig macht?

Bitte nur C++ 14 Lösungen.

#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

'has_isa' wird nicht automatisch versuchen, die Spezialisierung mit' has_dep' zu instanziieren, anstatt mit 'void', falls Sie keinen zweiten Template-Parameter angeben. – Albjenow

+0

@Albjenow dieser Ansatz funktioniert, wenn Funktionselement nicht Vorlage ist. Versuchen Sie geringfügige Änderungen am Code (entfernen Sie Template C und fix has_dep) und Sie werden sehen, dass alles andere korrekt ist. Es sollte versuchen, es zu tun, weil mehr eingeschränkte Spezialisierung immer gegen weniger eingeschränkt gewinnt –

Antwort

4

Es gibt zwei Probleme in diesem Programm.


Zuerst has_dep<XX> ist bool. Wenn wir versuchen has_dep<XX>, das Hinzufügen der Standard-Vorlage Argumente bedeutet, dass dies wirklich has_dep<XX, void> ist. Aber die Spezialisierung ist has_dep<XX, bool> - was nicht dem entspricht, was wir tatsächlich suchen. bool stimmt nicht mit void überein. Deshalb ist has_dep<XX> . Die Lösung dafür ist std::void_t, und ich würde vorschlagen, dass Sie durchlesen, dass Q/A eine Idee für warum es funktioniert. In Ihrer Spezialisierung müssen Sie stattdessen void_t<has_dep<ObjType>> verwenden.


Zweitens ist das nicht richtig:

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

SFINAE geschieht nur im unmittelbaren Zusammenhang mit der Substitution und Klasse Template-Parameter sind nicht in dem unmittelbaren Zusammenhang mit der Funktion Vorlage Substitution. Die richtigen Muster hier sind:

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

Machen diese beiden Korrekturen sowie das Programm funktioniert wie vorgesehen.

+0

void_t ist C++ 17, aber ich habe die Idee, danke, ich kann definitiv meinen eigenen Wrapper schreiben, der ähnlich ist –

2

Ihre sfinae schlägt fehl, weil has_isa die falsche Spezialisierung auswählt.

Die Verwendung von has_isa<T> muss entweder die Standardimplementierung oder die spezialisierte Version sein.

Wie Sie definiert, haben Sie ein Standardargument für ungültig zu erklären:

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

Dann im Ausdruck has_isa<T>, muss der zweite Parameter ungültig. Es ist ungefähr das gleiche wie das Schreiben has_isa<T, void>.

Das Problem ist folgendes:

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

Auch wenn Teilvorlage Ordnung hielte diese „Überlast“ speziellere, es wird nicht gewählt werden. Schauen Sie sich die Definition von has_dep:

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

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

Hey, der Typ has_dep<T> ist der Rückgabetyp von t.isA<void>() die bool ist!

So ist die spezielle Version wie folgt aussehen:

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

hierfür Also, um zu arbeiten, müssen Sie nennen. Da dies nicht praktikabel ist, sollten Sie Ihre Spezialisierung wie folgt definieren:

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

Wo void_t wie so definiert:

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

Als solche has_isa<T> immer die Spezialisierung betrachten, weil wir void als die schicken 2. Template-Parameter, und nun ergibt sich unsere Spezialisierung immer mit void als zweiter Parameter.

Auch, wie von Barry angegeben, wird Ihre Funktion nicht korrekt gebildet, da sfinae nur im unmittelbaren Kontext erscheint.Sie sollten es so schreiben:

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

Wenn Sie die Template-Parameter setzen Sie nicht wollen, einfach die Funktion machen private:

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

Ihr Problem ist, dass Sie die falsche Klasse spezialisieren:

Sie sollten has_dep zwingen, void zurückzugeben.

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

Also hier

template <typename ObjType> 
struct has_isa<ObjType, has_dep<ObjType> > : public true_type {}; 
// It is really <bjType, void> you specialize. 
Verwandte Themen