2013-03-15 4 views
24

Ich habe das SFINAE-Idiom schon einige Male benutzt und ich habe mich daran gewöhnt, meine std::enable_if<> in Template-Parameter anstatt in Rückgabetypen zu setzen. Ich stieß jedoch auf einen trivialen Fall, in dem es nicht funktionierte, und ich bin mir nicht sicher warum. Zunächst einmal ist da, mein Haupt:SFINAE funktioniert im Rückgabetyp, aber nicht als Template-Parameter

int main() 
{ 
    foo(5); 
    foo(3.4); 
} 

Hier eine Implementierung von foo ist, dass der Fehler auslöst:

template<typename T, 
     typename = typename std::enable_if<std::is_integral<T>::value>::type> 
auto foo(T) 
    -> void 
{ 
    std::cout << "I'm an integer!\n"; 
} 

template<typename T, 
     typename = typename std::enable_if<std::is_floating_point<T>::value>::type> 
auto foo(T) 
    -> void 
{ 
    std::cout << "I'm a floating point number!\n"; 
} 

Und hier ist ein angeblich gleichwertig Stück Code, das funktioniert gut:

template<typename T> 
auto foo(T) 
    -> typename std::enable_if<std::is_integral<T>::value>::type 
{ 
    std::cout << "I'm an integrer!\n"; 
} 

template<typename T> 
auto foo(T) 
    -> typename std::enable_if<std::is_floating_point<T>::value>::type 
{ 
    std::cout << "I'm a floating point number!\n"; 
} 

Meine Frage ist: Warum löst die erste Implementierung von foo diesen Fehler aus, während der zweite es nicht auslöst?

main.cpp:14:6: error: redefinition of 'template<class T, class> void foo(T)' 
auto foo(T) 
    ^
main.cpp:6:6: note: 'template<class T, class> void foo(T)' previously declared here 
auto foo(T) 
    ^
main.cpp: In function 'int main()': 
main.cpp:23:12: error: no matching function for call to 'foo(double)' 
    foo(3.4); 
      ^
main.cpp:6:6: note: candidate: template<class T, class> void foo(T) 
auto foo(T) 
    ^
main.cpp:6:6: note: template argument deduction/substitution failed: 
main.cpp:5:10: error: no type named 'type' in 'struct std::enable_if<false, void>' 
      typename = typename std::enable_if<std::is_integral<T>::value>::type> 
     ^

EDIT:

Working code und faulty code.

+1

Ok. Tatsächliche Demonstration: [1. Teil konnte nicht kompiliert werden] (http://ideone.com/mJ8Zp6) und [2. erfolgreich kompilierter Teil] (http://ideone.com/G0jBft). –

+0

Zusätzliche Informationen: gleich mit VS 2012 November CTP. –

+3

[This] (http://flamingdangerzone.com/cxx11/2012/06/01/almost-static-if.html) sollte das perfekte Buch für Sie sein. – Xeo

Antwort

26

Sie sollten einen Blick auf 14.5.6.1 Function template overloading (C++ 11 Standard) werfen, wo die Äquivalenz von Funktionsvorlagen definiert ist. Kurz gesagt, Standardschablonenargumente werden nicht berücksichtigt, sodass Sie im ersten Fall dieselbe Funktionsschablone zweimal definiert haben. Im zweiten Fall haben Sie einen Ausdruck, der auf Template-Parameter verweist, die im Rückgabetyp verwendet werden (siehe auch 14.5.6.1/4). Da dieser Ausdruck Teil der Signatur ist, erhalten Sie zwei verschiedene Funktionsschablonendeklarationen und somit erhalten SFINAE eine Chance zu arbeiten.

+0

Vielen Dank. Diese Erklärung ist zumindest einfach und klar. Ich hatte keine Ahnung von dieser Regel :) – Morwenn

7

Die = ... der Vorlage gibt nur einen Standardparameter. Dies ist nicht Teil der eigentlichen Signatur, die

template<typename T, typename> 
auto foo(T a); 

für beiden Funktionen wie

aussieht.

Je nach Ihren Anforderungen ist die generische Lösung für dieses Problem die Tag-Verteilung.

struct integral_tag { typedef integral_tag category; }; 
struct floating_tag { typedef floating_tag category; }; 

template <typename T> struct foo_tag 
: std::conditional<std::is_integral<T>::value, integral_tag, 
        typename std::conditional<std::is_floating_point<T>::value, floating_tag, 
               std::false_type>::type>::type {}; 

template<typename T> 
T foo_impl(T a, integral_tag) { return a; } 

template<typename T> 
T foo_impl(T a, floating_tag) { return a; } 

template <typename T> 
T foo(T a) 
{ 
    static_assert(!std::is_base_of<std::false_type, foo_tag<T> >::value, 
       "T must be either floating point or integral"); 
    return foo_impl(a, typename foo_tag<T>::category{}); 
} 

struct bigint {}; 
template<> struct foo_tag<bigint> : integral_tag {}; 

int main() 
{ 
    //foo("x"); // produces a nice error message 
    foo(1); 
    foo(1.5); 
    foo(bigint{}); 
} 
+2

Dies ist nicht sehr allgemein - es ist spezifisch für diesen genauen Fall - Ganzzahl vs Floating. – einpoklum

2

Werte in den Vorlagen arbeiten:

template<typename T, 
     typename std::enable_if<std::is_integral<T>::value, int>::type = 0> 
auto foo(T) 
    -> void 
{ 
    std::cout << "I'm an integer!\n"; 
} 

template<typename T, 
     typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0> 
auto foo(T) 
    -> void 
{ 
    std::cout << "I'm a floating point number!\n"; 
}