template - plantillas c++>
SFINAE funciona de manera diferente en los casos de parĂ¡metros de tipo y no tipo de plantilla (5)
¿Por qué funciona este código?
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) {}
y puede distinguir correctamente entre estas dos llamadas:
Add(1);
Add(1.0);
¿mientras que el siguiente código, si se compila, da como resultado la redefinición del error Add () ?
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) {}
Entonces, si el parámetro de plantilla es de tipo, entonces tenemos una redefinición de la función, si no es de tipo, entonces todo está bien.
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.
Así que cuando escribes:
template<
typename T,
typename = std::enable_if_t<std::is_same<T, int>::value, T>>
void Add(T) {}
Está bien, pero cuando escribes:
template<
typename T,
typename = std::enable_if_t<!std::is_same<T, int>::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, de ahí el error.
Si desea tener varias implementaciones como sugiere su pregunta, debe usar std::enable_if_t
como el parámetro de retorno de su plantilla o usarlo de la misma manera que su primer ejemplo. Entonces tu código inicial se convierte en:
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) {}
Ejemplo de trabajo en Coliru
En el código anterior, si T == int
, la segunda firma se vuelve inválida y eso activa a SFINAE.
NB: Supongamos que quieres N implementaciones. Puede usar el mismo truco que el anterior, pero deberá asegurarse de que solo un booleano entre los N sea verdadero y los N-1 que permanezcan son falsos, de lo contrario obtendrá el mismo error.
Creo que el problema es el hecho de que puede usar una función incluso si un parámetro de plantilla predeterminado no se compila especificando un valor diferente para ella. Piense en lo que sucedería si especificara dos parámetros de plantilla en una llamada para agregar.
Primero trataré de dar un ejemplo sin el uso de plantillas, pero con argumentos predeterminados. El siguiente ejemplo es comparable a por qué falla el segundo de los suyos, aunque no es indicativo del funcionamiento interno de la resolución de sobrecarga de plantillas.
Tienes dos funciones declaradas como tales:
void foo(int _arg1, int _arg2 = 3);
Y
void foo(int _arg1, int _arg2 = 4);
Esperemos que te des cuenta de que esto no se compilará, nunca será una forma de distinguir entre las dos llamadas a foo
con el argumento predeterminado. Es completamente ambiguo, ¿cómo sabrá el compilador qué valor predeterminado elegir? Tal vez se pregunte por qué usé este ejemplo, después de todo, ¿no debería la plantilla del primer ejemplo deducir tipos diferentes? La respuesta corta a esta pregunta 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<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) {}
... tienen exactamente la misma firma, que es:
template<typename T,typename>
void Add(T);
Y (respectivamente)
template <typename T, typename>
void Add(T);
Ahora debe comenzar a comprender la similitud entre el ejemplo que di con las no plantillas y el ejemplo que proporcionó que falló.
SFINAE no se propaga a los valores predeterminados ni para los tipos ni para los valores. Sólo los tipos de argumentos de función y el resultado se utilizan en esta técnica.
SFINAE se trata de la sustitución. Así que vamos a sustituir!
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) {}
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 los fallos que obtengamos:
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 enreda:
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) {}
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 las mismas firmas de plantilla. Y eso no está permitido, error generado.
¿Por qué hay tal regla? Más allá del alcance de esta respuesta. Simplemente estoy demostrando cómo los dos casos son diferentes y afirmando que el estándar los trata de manera diferente.
Cuando utiliza un parámetro de plantilla no 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 tipo de plantilla como el anterior, solo cambia los valores de los parámetros de la plantilla.