SFINAE funciona de manera diferente en los casos de tipo y parámetros de plantilla sin tipo

Por qué funciona este código:

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

y puede distinguir correctamente entre estas dos llamadas:

 Add(1); Add(1.0); 

mientras que el siguiente código, si se comstack, ¿da como resultado la redefinición del error Add () ?

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

Entonces, si el parámetro de plantilla es de tipo, entonces tenemos la redefinición de la función, si no es de tipo, entonces todo está bien.

SFINAE es sobre la sustitución. Así que vamos a sustituir!

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

Se convierte en:

 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) {} 

Eliminar las fallas que obtenemos:

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

Ahora elimine los valores de los parámetros de la plantilla :

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

Estas son diferentes plantillas.

Ahora el que se equivoca:

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

Se convierte en:

 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) {} 

Eliminar fallas:

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

Y ahora los valores de los parámetros de la plantilla:

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

Estas son la misma firma de plantilla. Y eso no está permitido, error generado.

¿Por qué hay tal regla? Más allá del scope de esta respuesta. Simplemente estoy demostrando cómo los dos casos son diferentes y afirmo que el estándar los trata de manera diferente.

Cuando utiliza un parámetro de plantilla sin tipo como el anterior, cambia la firma de la plantilla no solo los valores de los parámetros de la plantilla. Cuando utiliza un parámetro de plantilla de tipo como el anterior, solo cambia los valores de los parámetros de la plantilla.

Aquí, el problema es que la firma de la plantilla para add() es la misma: una plantilla de función que toma dos tipos de parámetros.

Entonces cuando escribes:

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

Está bien, pero cuando escribes:

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

Está redefiniendo la primera plantilla add() , solo que esta vez especifica un tipo predeterminado diferente para el segundo parámetro de plantilla: al final, definió una sobrecarga para add() con la misma firma exacta, de ahí el error.

Si desea tener varias implementaciones, como sugiere su pregunta, debe usar std::enable_if_t como parámetro de devolución de su plantilla o usarla de la misma manera que su primer ejemplo. Entonces su código inicial se convierte en:

 template std::enable_if_t::value> Add(T) {} template std::enable_if_t::value> Add(T) {} 

Ejemplo de trabajo en Coliru

En el código anterior, si T == int , la segunda firma deja de ser válida y desencadena SFINAE.

NB: supongamos que quiere N implementaciones. Puedes usar el mismo truco que el anterior, pero necesitarás asegurarte de que solo un booleano entre los N sea verdadero y los N-1 que permanezcan sean falsos, de lo contrario obtendrás exactamente el mismo error.

Creo que el problema es que puede usar una función incluso si un parámetro de plantilla predeterminado no se comstack especificando un valor diferente para él. Piense en lo que sucedería si especificara dos parámetros de plantilla en una llamada para agregar.

Primero intentaré dar un ejemplo sin el uso de plantillas, pero con argumentos predeterminados. El siguiente ejemplo es comparable a por qué falla el segundo ejemplo suyo, aunque no es indicativo del funcionamiento interno de la resolución de sobrecarga de la plantilla.

Usted tiene dos funciones declaradas como tales:

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

Y

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

Esperemos que se dé cuenta de que esto no comstackrá, nunca será una forma de distinguir entre las dos llamadas a foo con el argumento predeterminado. Es completamente ambiguo, ¿cómo sabe el comstackdor qué predeterminado elegir? Puede preguntarse por qué utilicé este ejemplo, después de todo, ¿no debería la plantilla del primer ejemplo deducir diferentes tipos? La respuesta corta a eso es no, y eso se debe a la “firma” de las dos plantillas en su segundo ejemplo:

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

… tienen exactamente la misma firma, que es:

 template void Add(T); 

Y (respectivamente)

 template  void Add(T); 

Ahora debería comenzar a entender la similitud entre el ejemplo que di con las no plantillas y el ejemplo que proporcionó que falló.

SFINAE no se propaga a valores predeterminados para tipos ni valores. En esta técnica solo se usan tipos de argumentos de función y resultado.