6

¿Hay alguna forma de especializar una plantilla como esta, por lo que la especialización se aplica solo si T tiene una función miembro hash? (Tenga en cuenta que esto es solo un ejemplo de lo que estoy tratando de hacer. Sé que tendría más sentido para cada clase que tiene una función hash comprobarlo solo en la función de miembro operator==, pero solo quiero saber si este tipo de cosas es posible.)¿Hay alguna forma de especializar una plantilla basada en los miembros de un parámetro en C++?

template <class T> 
bool equals(const T &x, const T &y) 
{ 
    return x == y; 
} 

template <class T> // somehow check if T has a member function 'hash' 
bool equals<T>(const T &x, const T &y) 
{ 
    return x.hash() == y.hash() && x == y; 
} 

Preferiría una solución pre-C++ 11 si es posible.

+1

Puede usar conceptos con el concepto de compilador experimentalGCC, pero eso no será el estándar C++ antes de unos años. – Morwenn

Respuesta

8

Aquí hay un ejemplo de mi propio código. Como puede adivinar a partir de uno de los nombres de estructura, esto se basa en el principio de que Substitution Failure is Not an Error. La estructura has_member_setOrigin define dos versiones de test. El primero no puede cumplirse si U no tiene un miembro setOrigin. Como ese es no es un error en una sustitución de plantilla simplemente actúa como si no existiera. El orden de resolución para funciones polimórficas encuentra así test(...) que de otro modo tendría una prioridad más baja. El value se determina por el tipo de devolución de test.

Esto es seguido por dos definiciones de callSetOrigin (equivalente a su equals) usando la plantilla enable_if. Si examina enable_if, verá que si el primer argumento de la plantilla es verdadero, se define enable_if<...>::type, de lo contrario no lo es. Esto nuevamente crea un error de sustitución en una de las definiciones de callSetOrigin, de manera que solo sobrevive uno.

template <typename V> 
struct has_member_setOrigin 
{ 
    template <typename U, void (U::*)(const Location &)> struct SFINAE {}; 
    template <typename U> static char test(SFINAE<U, &U::setOrigin>*); 
    template <typename U> static int test(...); 
    static const bool value = sizeof(test<V>(0)) == sizeof(char); 
}; 

template<typename V> 
void callSetOrigin(typename enable_if <has_member_setOrigin<V>::value, V>::type &p, const Location &loc) const 
{ 
    p.setOrigin(loc); 
} 

template<typename V> 
void callSetOrigin(typename enable_if <!has_member_setOrigin<V>::value, V>::type &p, const Location &loc) const 
{ 
} 

olvidado que proporcionó una definición de enable_if así:

#ifndef __ENABLE_IF_ 
#define __ENABLE_IF_ 

template<bool _Cond, typename _Tp> 
struct enable_if 
{ }; 

template<typename _Tp> 
struct enable_if<true, _Tp> 
{ typedef _Tp type; }; 

#endif /* __ENABLE_IF_ */ 
+0

¡Es una gran solución! – Matt

+2

Esta es una buena respuesta, pero sería mejor si agregaste alguna explicación de cómo funciona. – zwol

2

Una solución C++ 11. Simplemente modifique el tipo de devolución para que solo sean válidos los tipos que tengan .hash. Además, utilizamos el operador de coma para que, mientras el compilador compruebe que puede calcular declval<T>.hash(), ignore eso y use el tipo de true, que es por supuesto bool, del tipo que desee.

template <class T> 
auto equals(const T &x, const T &y) -> decltype(declval<T>.hash(), true) 
{ 
    return x.hash() == y.hash() && x == y; 
} 

Creo que esto se llama Expression SFINAE.

Más detalles:

decltype(X,Y) es la misma que decltype(Y) (gracias al operador coma). Eso significa que mi tipo de devolución aquí es básicamente decltype(true), es decir, bool, según lo desee. Entonces, ¿por qué tengo declval<T>.hash()? ¿No es solo una pérdida de espacio?

La respuesta es que es la prueba que T tiene un método hash. Si esta prueba falla, entonces el cálculo del tipo de retorno falla, y por lo tanto la función no se ve como una sobrecarga válida y el compilador buscará en otra parte.

Por último, si usted no ha visto declval antes, es una manera muy útil para crear un objeto de tipo T en un contexto sin evaluar (como decltype).Es posible que tenga la tentación de escribir T() para construir un T y, por lo tanto, use T().hash() para llamar al hash. Pero eso no funcionará si T no tiene un constructor predeterminado. declval resuelve esto.

Cuestiones relacionadas