SFINAE trabaja en tipo de retorno pero no como parámetro de plantilla

Ya usé el modismo SFINAE varias veces y me acostumbré a poner mi std::enable_if en los parámetros de plantilla en lugar de en los tipos de devolución. Sin embargo, me encontré con un caso trivial en el que no funcionó, y no estoy seguro de por qué. Primero que nada, aquí está mi principal:

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

Aquí hay una implementación de foo que desencadena el error:

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

Y aquí hay una pieza de código supuestamente equivalente que funciona bien:

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

Mi pregunta es: ¿por qué la primera implementación de foo desencadena ese error mientras que el segundo no lo desencadena?

 main.cpp:14:6: error: redefinition of 'template void foo(T)' auto foo(T) ^ main.cpp:6:6: note: 'template 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 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' typename = typename std::enable_if<std::is_integral::value>::type> ^ 

EDITAR :

Código de trabajo y código defectuoso .

Debería echarle un vistazo a la 14.5.6.1 Function template overloading (estándar C ++ 11) donde se define la equivalencia de las plantillas de funciones. En resumen, los argumentos de la plantilla por defecto no se consideran, por lo que en el primer caso tiene la misma plantilla de función definida dos veces. En el segundo caso, tiene parámetros de plantilla de referencia de expresión utilizados en el tipo de devolución (consulte también 14.5.6.1/4). Como esta expresión es parte de la firma, se obtienen dos declaraciones de plantilla de función diferentes y, por lo tanto, SFINAE tiene la oportunidad de funcionar.

El = ... de la plantilla simplemente da un parámetro predeterminado. Esto no es parte de la firma real que se parece a

 template auto foo(T a); 

para ambas funciones.

Dependiendo de sus necesidades, la solución más genérica para este problema es usar el despacho de tags.

 struct integral_tag { typedef integral_tag category; }; struct floating_tag { typedef floating_tag category; }; template  struct foo_tag : std::conditional::value, integral_tag, typename std::conditional::value, floating_tag, std::false_type>::type>::type {}; template T foo_impl(T a, integral_tag) { return a; } template T foo_impl(T a, floating_tag) { return a; } template  T foo(T a) { static_assert(!std::is_base_of >::value, "T must be either floating point or integral"); return foo_impl(a, typename foo_tag::category{}); } struct bigint {}; template<> struct foo_tag : integral_tag {}; int main() { //foo("x"); // produces a nice error message foo(1); foo(1.5); foo(bigint{}); } 

Los valores en el trabajo de plantillas:

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