especialización explícita de la función de miembro de clase de plantilla

88

Necesito especializar la función de miembro de plantilla para algún tipo (digamos doble ). Funciona bien, mientras que la clase en Xsí no es una clase de plantilla, pero cuando la hago, la plantilla GCC comienza a dar errores en tiempo de compilación.

#include <iostream>
#include <cmath>

template <class C> class X
{
public:
   template <class T> void get_as();
};

template <class C>
void X<C>::get_as<double>()
{

}

int main()
{
   X<int> x;
   x.get_as();
}

Aquí está el mensaje de error

source.cpp:11:27: error: template-id
  'get_as<double>' in declaration of primary template
source.cpp:11:6: error: prototype for
  'void X<C>::get_as()' does not match any in class 'X<C>'
source.cpp:7:35: error: candidate is:
  template<class C> template<class T> void X::get_as()

¿Cómo puedo solucionarlo y cuál es el problema aquí?

Gracias por adelantado.

ledokol
fuente
2
esto es ilegal en el estándar actual, para especializarse, también debe especializar la clase ...
Nim
pero funciona si la clase no es plantilla. ¿También es ilegal?
ledokol
no, eso está perfectamente bien, es solo para las plantillas de clase que se aplica esta regla (AFAIK).
Nim

Respuestas:

108

No funciona de esa manera. Usted tendría que decir lo siguiente, pero es no correcta

template <class C> template<>
void X<C>::get_as<double>()
{

}

Los miembros explícitamente especializados necesitan que sus plantillas de clases circundantes también estén explícitamente especializadas. Por lo tanto, debe decir lo siguiente, que solo especializaría al miembro X<int>.

template <> template<>
void X<int>::get_as<double>()
{

}

Si desea mantener la plantilla circundante sin especializar, tiene varias opciones. Prefiero las sobrecargas

template <class C> class X
{
   template<typename T> struct type { };

public:
   template <class T> void get_as() {
     get_as(type<T>());
   }

private:
   template<typename T> void get_as(type<T>) {

   }

   void get_as(type<double>) {

   }
};
Johannes Schaub - litb
fuente
¿Por qué necesitas la type<>envoltura? ¿No podría Thacer el truco una conversión de un 0 a un puntero de tipo ? Supongo que no es tan elegante ...
Nim
Parece que esto realmente no es posible. Gracias.
ledokol
3
@Nim correcto, creo que la cosa del lanzamiento del puntero es fea, y no funcionaría para los tipos a los que no puede formar punteros (referencias). Además, hacer que un parámetro de función sea un puntero a un tipo de matriz sin un tamaño es ilegal en C ++. Tenerlo en una envoltura de tipo hace que funcione para todos los tipos.
Johannes Schaub - litb
2
@ JohannesSchaub-litb: ¿Cuáles son las otras opciones a lo largo de las sobrecargas? ¿Puedes mostrar algunos de ellos?
Jean-Bernard Jansen
2
@ reflejos rápidos su comentario fue usar template<typename T> void get_as(T*); void get_as(double*);y pasar un (T*)0.
Johannes Schaub - litb
24

Si uno puede usar std::enable_if, podríamos confiar en SFINAE (la falla de sustitución no es un error)

que funcionaría así (ver EN VIVO ):

#include <iostream>
#include <type_traits>

template <typename C> class X
{
public:
    template <typename T, 
              std::enable_if_t<!std::is_same_v<double,T>, int> = 0> 
    void get_as() { std::cout << "get as T" << std::endl; }

    template <typename T, 
              std::enable_if_t<std::is_same_v<double,T>, int> = 0> 
    void get_as() { std::cout << "get as double" << std::endl; }
};

int main() {
   X<int> d;
   d.get_as<double>();

   return 0;
}

Lo feo es que, con todos estos enable_if, solo una especialización necesita estar disponible para el compilador, de lo contrario surgirá un error de desambiguación. Es por eso que el comportamiento predeterminado "obtener como T" también necesita habilitar si.

Gabriel
fuente
Actualicé la publicación para hacerla más moderna.
Gabriel