¿Cómo puedo realizar diferentes acciones para diferentes parámetros de plantilla en una clase de plantilla?

Si quiero crear una clase de plantilla y, según el tipo de identificación del parámetro de plantilla, realizar diferentes acciones, ¿cómo codifico esto?

Por ejemplo, tengo la siguiente clase de plantilla, en la que quiero inicializar los datos del campo miembro dependiendo de si es un int o una cadena.

#include <string>

template <class T>
class A
{
private:
    T data;
public:
    A();
};

// Implementation of constructor
template <class T>
A<T>::A()
{
    if (typeid(T) == typeid(int))
    {
        data = 1;
    }
    else if (typeid(T) == typeid(std::string))
    {
        data = "one";
    }
    else
    {
        throw runtime_error("Choose type int or string");
    }
}

Sin embargo, este código no se compilaría con el siguiente archivo principal.

#include "stdafx.h"
#include "A.h"
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
    A<int> one;
    return 0;
}

El error es: error C2440: '=': no ​​se puede convertir de 'const char [2]' a 'int', lo que significa que el código en realidad está verificando la declaración else-if para un int, aunque nunca podrá llegar a esa parte del código.

A continuación, siguiendo este ejemplo (Realizar diferentes métodos basados ​​en el tipo de variable de plantilla), probé el siguiente archivo Ah, pero obtuve varios errores del enlazador que mencionan que A(void) ya está definido en A.obj.

#include <string>

template <class T>
class A
{
private:
    T data;
public:
    A();
    ~A();
};

// Implementation of constructor
template <>
A<int>::A()
{
    data = 1;
}
template <>
A<std::string>::A()
{
    data = "one";
}

¿Alguien sabe cómo poner en marcha este código? También me doy cuenta de que usar una declaración if-else de este tipo en una clase de plantilla podría eliminar el poder de una plantilla. ¿Hay una mejor manera de codificar esto?

EDITAR: después de la discusión con Torsten (abajo), ahora tengo el siguiente archivo Ah:

#pragma once

#include <string>

// Class definition
template <class T>
class A
{
public:
    A();
    ~A();
private:
    T data;
};

// Implementation of initialization
template < class T > 
struct initial_data
{
  static T data() { throw runtime_error("Choose type int or string"); }
};

template <> 
struct initial_data< int >
{
    static int data() { return 1; }
};

template <> 
struct initial_data< std::string >
{
    static std::string data() { return "one"; }
};

// Definition of constructor
template <class T>
A<T>::A()
  : data( initial_data< T >::data() ) 
{
}

y los siguientes principales:

#include "stdafx.h"
#include "A.h"
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
    A<int> ione;

    return 0;
}

El error del enlazador que recibo ahora es: Plantilla de prueba 4.obj: error LNK2019: símbolo externo no resuelto "público: __thiscall A::~A(void)" (??1?$A@H@@QAE@XZ) al que se hace referencia en función _wmain

preguntado el 04 de julio de 12 a las 09:07

1) No use _tmain mierda y amigos. 2) ¿Cuál es el punto de usar typeid con plantillas? El código simplemente no se compilará para T = int, porque no hay int::operator=(const char *)... 3) Testcase para ese error de compilación, por favor. -

La especialización de plantilla que hiciste como el segundo ejemplo es el camino a seguir en mi humilde opinión... ¿agregaste protecciones de inclusión a tu archivo de encabezado? Por el error del enlazador, parece que no lo hiciste. -

Creo que podrías usar SFINAE para esto. Con enable_if e is_same podría tener diferentes funciones de miembro basadas en parámetros de plantilla. -

@jrok: Nunca he oído hablar de SFINAE, pero le echaré un vistazo. -

@Naveen: tengo protectores de inclusión en todos mis archivos de encabezado, por lo que esa no puede ser la causa. -

3 Respuestas

Las especializaciones explícitas son el camino a seguir.

Supongo que está incluyendo su Ah en varios .cpp, y esa es la causa raíz de su problema.

Las especializaciones son definiciones y debe haber solo una definición de A::A() y A::A(), por lo que deben estar en un solo .cpp.

Tendrás que mover la especialización explícita en un .cpp

template <>
A<int>::A()
{
    data = 1;
}
template <>
A<std::string>::A()
{
    data = "one";
}

y guarda para ellos una declaración en Ah

template<> A<int>::A();
template<> A<std::string>::A();

para que el compilador sepa que están explícitamente especializados y no intente agregar uno automático.

Editar: con estos cuatro archivos, g ++ m.cpp f.cpp a.cpp no ​​muestra ningún error.

// a.h
#define A_H

#include <string>

template <class T>
class A
{
private:
    T data;
public:
    A();
};

template<> A<int>::A();
template<> A<std::string>::A();

#endif

// a.cpp
#include "a.h"

template <>
A<int>::A()
{
    data = 1;
}
template <>
A<std::string>::A()
{
    data = "one";
}

// f.cpp
#include "a.h"

int f()
{
    A<int> one;
    A<std::string> two;
}

// m.cpp
#include "a.h"

int f();

int main()
{
    A<int> one;
    A<std::string> two;
    f();
}

Respondido 05 Jul 12, 10:07

Gracias por tu respuesta. Aunque todavía no lo consigo funcionar. Dice que coloca las declaraciones en el archivo de encabezado, pero ¿lo coloca en la clase? Si lo coloco dentro de la clase, el compilador dice: error C2931: 'A ' : template-class-id redefinido como una función miembro de 'A '. Si lo coloco fuera de la clase, el enlazador dice: error LNK2005: "public: __thiscall A ::A (int,int)" (??0?$A@H@@QAE@HH@Z) ya definido en A.obj - atracción física

¡Eso funciona, es claro y es exactamente lo que estoy buscando! :-D - atracción física

Tiene razón en la segunda solución, lo que necesita es especialización de plantilla (mantener la declaración y la implementación juntas):

#include <string>

template <class T>
class A
{
private:
    T data;
public:
    A();
    ~A();
};

template <>
class A <std::string>
{
private:
  std::string data;
public:
  A() { data = "one"; }
};

template <>
class A <int>
{
private:
  int data;
public:
  A() { data = 1; }
};

Si puedo sugerir una solución más elegante, agregaría un parámetro al constructor y evitaría la especialización de la plantilla:

template <class T>
class A
{
private:
    T data;
public:
    A( T value ) : data( value ) {}
    virtual ~A() {}
};

Respondido 04 Jul 12, 10:07

Gracias por tu respuesta. Con respecto a su primera solución: ¿cómo puedo llamar a métodos genéricos en las clases especializadas? Más específico: agregué un método GetData() en la clase genérica y escribí en main: A número(); cout << A.GetData(); El compilador da el error C2039: 'GetData' no es miembro de 'A '. ¿Como puedo resolver esto? En cuanto a su segunda solución: eso es muy elegante. Sin embargo, mi problema real es un poco más complicado, los datos de campo se inicializan llamando a un método según el tipo de entrada. Es por eso que quería usar la especialización de plantilla. - atracción física

Al usar plantillas con clases, el compilador genera una clase completamente diferente para cada especialización. La clase de plantilla genérica y la clase de plantilla especializada no son como la clase base y la clase derivada. Por lo tanto, debe volver a implementar el método GetData() en cada especialización, a menos que use la segunda solución. - Sdra

En caso de que sea solo el c'tor donde desea tener un comportamiento que dependa de T, sugeriría factorizar esto en una estructura diferente:

template < class T > 
struct initial_data
{
  static T data() { throw runtime_error("Choose type int or string"); }
};

template <> 
struct initial_data< int >
{
    static int data() { return 1; }
}

template <> 
struct initial_data< std::string >
{
    static std::string data() { return "1"; }
}

Si especializa una clase en su parámetro de plantilla, las diferentes especializaciones son tipos totalmente diferentes y pueden tener diferentes conjuntos de datos y funciones.

Por último:

template <class T>
A<T>::A()
  : data( initial_data< T >::data() ) 
{
}

Saludos amables Torsten

Respondido 04 Jul 12, 10:07

Gracias por tu respuesta. Ahora tengo el código anterior en el archivo de encabezado (incluidos los puntos y comas después de las estructuras). Lo coloqué después de la definición de clase. Con el principal anterior, sigo recibiendo errores del enlazador LNK 2019. ¿Este código funciona para usted? ¿Tengo el pedido correcto? - atracción física

@physicalattraction Si tuviera el orden incorrecto, el compilador se quejaría. LNK2019 es un error del enlazador que se queja de que falta un símbolo externo. Tal vez pueda proporcionarnos un ejemplo muy pequeño que reproduzca el problema, incluida la redacción exacta del mensaje de error. - Torsten Robitzki

He editado la publicación de apertura con el código tal como lo tengo ahora. No veo ningún error. ¿Tal vez puedas verlo? - atracción física

@physicalattraction Declaró un destructor para A, pero no definió uno. Y eso es de lo que se queja el enlazador. Agregue una definición para el destructor o elimine la declaración :-) - Torsten Robitzki

¡Eso funciona! Ahora tengo el código completo ejecutándose con su solución y la de AProgrammer. - atracción física

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.