SFINAE trabalhando no tipo de retorno, mas não como parâmetro de modelo

Eu já usei o idioma SFINAE algumas vezes e me acostumei a colocar meus std::enable_if em parâmetros de template ao invés de em tipos de retorno. No entanto, deparei-me com um caso trivial em que não funcionou e não sei porquê. Primeiro de tudo, aqui é meu principal:

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

Aqui está uma implementação do foo que aciona o erro:

 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"; } 

E aqui está um pedaço de código supostamente equivalente que funciona bem:

 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"; } 

Minha pergunta é: por que a primeira implementação do foo triggers esse erro enquanto o segundo não o aciona?

 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 trabalho e código defeituoso .

Você deve dar uma olhada na 14.5.6.1 Function template overloading (padrão C ++ 11) onde a equivalência dos modelos de function é definida. Em suma, os argumentos padrão do template não são considerados, então no primeiro caso você tem o mesmo template de function definido duas vezes. No segundo caso, você tem parâmetros de modelo de referência de expressão usados ​​no tipo de retorno (novamente, ver 14.5.6.1/4). Como essa expressão faz parte da assinatura, você recebe duas declarações de modelo de function diferentes e, assim, a SFINAE tem a chance de funcionar.

O = ... do modelo apenas fornece um parâmetro padrão. Isso não faz parte da assinatura real que parece

 template auto foo(T a); 

para ambas as funções.

Dependendo de suas necessidades, a solução mais genérica para esse problema é usar o 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{}); } 

Valores nos modelos funcionam:

 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"; }