2016-04-08 7 views
18

Warum funktioniert dieser Code Arbeit:SFINAE anders in Fällen von Typ und Nicht-Typ-Vorlage funktioniert Parameter

template< 
    typename T, 
    std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr> 
void Add(T) {} 

template< 
    typename T, 
    std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr> 
void Add(T) {} 

und zwischen diesen beiden Anrufe richtig unterscheiden kann:

Add(1); 
Add(1.0); 

während der folgende Code, wenn kompilierte Ergebnisse in der Neudefinition von Add() Fehler?

template< 
    typename T, 
    typename = typename std::enable_if<std::is_same<T, int>::value, T>::type> 
void Add(T) {} 

template< 
    typename T, 
    typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type> 
void Add(T) {} 

Wenn also die Template-Parameter-Typ ist, dann haben wir Neubestimmung der Funktion, wenn es nicht-Typ ist, dann ist alles in Ordnung.

+7

Stellen Sie sich vor, Sie schreiben zwei Funktionen 'Überladungen' wie diese: 'void Add (int = 0);' und 'void Add (int = 1)'. Sind sie wirklich anders? – Constructor

+1

Mögliches Duplikat von [SFINAE für die Funktion der Klassenmitglieder (eine kompiliert die andere nicht)] (https://stackoverflow.com/questions/31625914/sfinae-for-class-member-function-one-compiles-the-other-not) – neonxc

Antwort

1

Ich denke, das Problem ist die Tatsache, dass Sie eine Funktion verwenden können, selbst wenn ein Standard-Template-Parameter nicht kompiliert, indem Sie einen anderen Wert dafür angeben. Stellen Sie sich vor, was passieren würde, wenn Sie in einem hinzuzufügenden Aufruf zwei Vorlagenparameter angeben würden.

0

SFINAE wird nicht auf Standardwerte für Typen oder Werte verteilt. In dieser Technik werden nur Arten von Funktionsargumenten und Ergebnissen verwendet.

2

Hier ist das Problem, dass die Vorlage Signatur für add() ist die gleiche: Eine Funktionsvorlage, die zwei Parameter Typ. So

, wenn Sie schreiben:

template< 
    typename T, 
    typename = std::enable_if_t<std::is_same<T, int>::value, T>> 
void Add(T) {} 

ist das in Ordnung, aber wenn Sie schreiben:

template< 
    typename T, 
    typename = std::enable_if_t<!std::is_same<T, int>::value, T>> 
void Add(T) {} 

Sie neu definieren die erste add() Vorlage, nur dieses Mal, wenn Sie einen anderen Standardtyp für die angeben zweiter Vorlagenparameter: Am Ende haben Sie eine Überladung für add() mit der exakt gleichen Signatur definiert, daher der Fehler.

Wenn Sie mehrere Implementierungen möchten, wie es Ihre Frage vorschlägt, sollten Sie als Rückgabeparameter Ihrer Vorlage verwenden oder sie auf die gleiche Weise wie in Ihrem ersten Beispiel verwenden. So Ihr ursprünglicher Code wird:

template<typename T> 
std::enable_if_t<std::is_same<T, int>::value> Add(T) {} 
template<typename T> 
std::enable_if_t<!std::is_same<T, int>::value> Add(T) {} 

Arbeitsbeispiel auf Coliru

Auf dem obigen Code, wenn T == int, die zweite Signatur ungültig wird und dass löst SFINAE.

Hinweis: Angenommen, Sie möchten N Implementierungen. Sie können den gleichen Trick wie oben verwenden, aber Sie müssen sicherstellen, dass nur ein Boolescher Wert zwischen den N wahr ist und der N-1, der übrig bleibt, falsch ist, sonst erhalten Sie genau den gleichen Fehler!

+0

@Constructor Ja, ich habe vergessen, das '!' Auf der zweiten Vorlage hinzuzufügen. Fest! – Rerito

17

Bei SFINAE geht es um Substitution. Also lass uns ersetzen!

template< 
    typename T, 
    std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr> 
void Add(T) {} 

template< 
    typename T, 
    std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr> 
void Add(T) {} 

Becomes:

template< 
    class T=int, 
    int* = nullptr> 
void Add(int) {} 

template< 
    class T=int, 
    Substitution failure* = nullptr> 
void Add(int) { 

template< 
    class T=double, 
    Substitution failure* = nullptr> 
void Add(double) {} 

template< 
    class T=double 
    double* = nullptr> 
void Add(double) {} 

Ausfälle entfernen wir bekommen:

template< 
    class T=int, 
    int* = nullptr> 
void Add(int) {} 
template< 
    class T=double 
    double* = nullptr> 
void Add(double) {} 

Jetzt entfernen Template-Parameter-Werte:

template< 
    class T, 
    int*> 
void Add(T) {} 
template< 
    class T 
    double*> 
void Add(T) {} 

Dies sind verschiedene Vorlagen

Nun ist die eine, die bis verunstaltet:

template< 
    typename T, 
    typename = typename std::enable_if<std::is_same<T, int>::value, T>::type> 
void Add(T) {} 

template< 
    typename T, 
    typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type> 
void Add(T) {} 

Becomes:

template< 
    typename T=int, 
    typename =int> 
void Add(int) {} 

template< 
    typename int, 
    typename = Substitution failure > 
void Add(int) {} 

template< 
    typename T=double, 
    typename = Substitution failure > 
void Add(double) {} 

template< 
    typename T=double, 
    typename = double> 
void Add(double) {} 

entfernen Ausfälle:

template< 
    typename T=int, 
    typename =int> 
void Add(int) {} 
template< 
    typename T=double, 
    typename = double> 
void Add(double) {} 

Und jetzt Template-Parameter-Werte:

template< 
    typename T, 
    typename> 
void Add(T) {} 
template< 
    typename T, 
    typename> 
void Add(T) {} 

Dies sind die gleichen Vorlagensignaturen. Und das ist nicht erlaubt, Fehler generiert.

Warum gibt es so eine Regel? Über den Rahmen dieser Antwort hinaus. Ich zeige einfach, wie die beiden Fälle unterschiedlich sind, und behaupte, dass der Standard sie unterschiedlich behandelt.

Wenn Sie einen nicht typisierten Vorlagenparameter wie den obigen verwenden, ändern Sie die Vorlagensignatur nicht nur die Vorlagenparameterwerte. Wenn Sie einen Typvorlagenparameter wie den obigen verwenden, ändern Sie nur die Vorlagenparameterwerte.

0

Ich werde versuchen, ein Beispiel ohne die Verwendung von Vorlagen, aber mit Standardargumenten geben. Das folgende Beispiel ist vergleichbar mit dem Grund, warum das zweite Beispiel von Ihnen fehlschlägt, obwohl es nicht auf die innere Funktionsweise der Template-Überladungsauflösung hinweist.

Sie haben zwei Funktionen als solche deklariert:

void foo(int _arg1, int _arg2 = 3); 

Und

void foo(int _arg1, int _arg2 = 4); 

Hoffentlich merkt man, dass dies zu kompilieren fehlschlagen, ihr nie ein Weg sein wird, zwischen den beiden zu unterscheiden Aufrufe an foo mit dem Standardargument. Es ist komplett mehrdeutig, wie wird der Compiler jemals wissen, welcher Standard zu wählen? Sie wundern sich vielleicht, warum ich dieses Beispiel benutzt habe, schließlich sollte die Vorlage im ersten Beispiel nicht verschiedene Typen ableiten? Die kurze Antwort ist nein, und das ist, weil die „Signatur“ der beiden Vorlagen in Ihrem zweiten Beispiel:

template< 
    typename T, 
    typename = typename std::enable_if<std::is_same<T, int>::value, T>::type> 
void Add(T) {} 

template< 
    typename T, 
    typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type> 
void Add(T) {} 

...haben genau die gleiche Signatur, die ist:

template<typename T,typename> 
void Add(T); 

und (jeweils)

template <typename T, typename> 
void Add(T); 

Jetzt sollten Sie beginnen, die Ähnlichkeit ist zwischen dem Beispiel zu verstehen gab ich mit Nicht-Vorlagen und das Beispiel, das Sie vorausgesetzt, dass gescheitert.

Verwandte Themen