Especialización de plantillas de un solo método de una clase con plantilla

92

Siempre considerando que el siguiente encabezado, que contiene mi clase con plantilla, está incluido en al menos dos .CPParchivos, este código se compila correctamente:

template <class T>
class TClass 
{
public:
  void doSomething(std::vector<T> * v);
};

template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
  // Do something with a vector of a generic T
}

template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
  // Do something with a vector of int's
}

Pero tenga en cuenta el en línea en el método de especialización. Es necesario para evitar un error del enlazador (en VS2008 es LNK2005) debido a que el método se define más de una vez. Entiendo esto porque AFAIK, una especialización de plantilla completa es lo mismo que una definición de método simple.

Entonces, ¿cómo elimino eso inline? El código no debe duplicarse en cada uso. Busqué en Google, leí algunas preguntas aquí en SO y probé muchas de las soluciones sugeridas, pero ninguna se creó con éxito (al menos no en VS 2008).

¡Gracias!

Chuim
fuente
4
¿Por qué quieres eliminar el inline? ¿Lo encuentra estéticamente desagradable? ¿Crees que cambia el significado de tu código?
Martin York
1
Porque si este método fuera "largo" y se usara en muchos lugares, obtendría su código binario copiado en todas partes, ¿verdad? Traté de explicar esto en la pregunta, pero supongo que no estaba claro ... :)
Chuim
@Martin: ¿Qué sucede si la implementación necesita muchos otros códigos que luego debe incluir este encabezado en lugar del archivo cpp?
sbi

Respuestas:

72

Al igual que con las funciones simples, puede usar la declaración y la implementación. Pon tu declaración de encabezado:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

y ponga la implementación en uno de sus archivos cpp:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

No olvide eliminar en línea (lo olvidé y pensé que esta solución no funcionará :)). Comprobado en VC ++ 2005

maxim1000
fuente
Intenté algo al menos similar a esto antes, pero obtuve otros errores, pero ahora que mencionaste que debo haber olvidado eliminar el proceso de inlinecopiar / pegar. ¡De esta manera funcionó!
Chuim
Lo mismo se aplica a las funciones sin plantilla (a diferencia de los métodos de clase). Recibía el mismo error de enlazador para mi especialización de función. Moví el cuerpo de la especialización de la función a un archivo .cpp y dejé la declaración de la especialización en el encabezado, y todo funcionó. ¡Gracias!
aldo
Me encontré con este problema y lo anterior me lo resolvió. Además, debe ocuparse de dónde el compilador expande el código de la plantilla. Si se hace dos veces, el compilador se queja de múltiples definiciones.
Diederik
4

Necesita mover la definición de especialización al archivo CPP. Se permite la especialización de la función miembro de la clase de plantilla incluso si la función no se declara como plantilla.

BostonLogan
fuente
3

No hay ninguna razón para eliminar la palabra clave en línea.
No cambia el significado del código de ninguna manera.

Martin York
fuente
Copiado del comentario de la pregunta: Porque si este método fuera "largo" y se usara en muchos lugares, obtendría su código binario copiado en todas partes, ¿verdad? Traté de explicar esto en la pregunta, pero supongo que no estaba claro ... :)
Chuim
1
No. El vinculador elimina las copias adicionales. Entonces, dentro de una aplicación o lib, solo tendría una instancia del método.
Martin York
3
Si la inlinepalabra clave da como resultado que la función esté realmente insertada (el estándar dice que el compilador debe tomarla como una pista), entonces esas copias adicionales no se pueden eliminar. Sin embargo, es solo una pista para insertar (su efecto principal es decir "no generar errores en las colisiones de enlaces de una manera particular")
Yakk - Adam Nevraumont
2

Si desea eliminar el inline por cualquier motivo, la solución de maxim1000 es perfectamente válida.

En tu comentario, sin embargo, parece que crees que la palabra clave en línea significa que la función con todo su contenido siempre está en línea, pero AFAIK, eso en realidad depende mucho de la optimización de tu compilador.

Citando las preguntas frecuentes de C ++

Hay varias formas de designar que una función está en línea, algunas de las cuales involucran la palabra clave en línea, otras no. No importa cómo designe una función como en línea, es una solicitud que el compilador puede ignorar: el compilador puede expandir en línea algunos, todos o ninguno de los lugares donde llama a una función designada como en línea. (No se desanime si eso parece irremediablemente vago. La flexibilidad de lo anterior es en realidad una gran ventaja: permite que el compilador trate las funciones grandes de manera diferente a las pequeñas, además permite que el compilador genere código que es fácil de depurar si selecciona las opciones correctas del compilador).

Entonces, a menos que sepa que esa función realmente inflará su ejecutable o que quiera eliminarlo del encabezado de definición de la plantilla por otras razones, puede dejarlo donde está sin ningún daño.

Triskeldeiano
fuente
1

Me gustaría agregar que todavía hay una buena razón para mantener la inlinepalabra clave allí si tiene la intención de dejar también la especialización en el archivo de encabezado.

"De manera intuitiva, cuando te especializas por completo en algo, ya no depende de un parámetro de plantilla, por lo que, a menos que realices la especialización en línea, debes ponerla en un archivo .cpp en lugar de .h o terminarás violando la regla de una definición ... "

Referencia: https://stackoverflow.com/a/4445772/1294184

Jordán
fuente
0

Esto es un poco de OT, pero pensé en dejar esto aquí en caso de que ayude a alguien más. Estaba buscando en Google sobre la especialización de plantillas, lo que me llevó aquí, y aunque la respuesta de @ maxim1000 es correcta y, en última instancia, me ayudó a resolver mis problemas, no pensé que estuviera muy claro.

Mi situación es un poco diferente (pero lo suficientemente similar como para dejar esta respuesta, creo) que la de los OP. Básicamente, estoy usando una biblioteca de terceros con diferentes tipos de clases que definen "tipos de estado". El corazón de estos tipos son simplemente enums, pero todas las clases heredan de un padre común (abstracto) y proporcionan diferentes funciones de utilidad, como la sobrecarga del operador y una static toString(enum type)función. Cada estado enumes diferente entre sí y no está relacionado. Por ejemplo, uno enumtiene los campos NORMAL, DEGRADED, INOPERABLE, otro tiene AVAILBLE, PENDING, MISSING, etc. Mi software se encarga de gestionar diferentes tipos de estados para diferentes componentes. Sucedió que quería utilizar las toStringfunciones para estosenumclases, pero como son abstractas no pude instanciarlas directamente. Podría haber extendido cada clase que quería usar, pero finalmente decidí crear una templateclase, donde typenamesería cualquier estado concreto que enumme importara. Probablemente se pueda tener algún debate sobre esa decisión, pero sentí que eso era mucho menos trabajo que extender cada enumclase abstracta con una personalizada e implementar las funciones abstractas. Y, por supuesto, en mi código, solo quería poder llamar .toString(enum type)y hacer que imprima la representación de cadena de eso enum. Dado que todos los enums no tenían ninguna relación, cada uno tenía su propiotoStringfunciones que (después de algunas investigaciones que aprendí) tenían que llamarse usando la especialización de plantillas. Eso me trajo aquí. A continuación se muestra un MCVE de lo que tuve que hacer para que esto funcione correctamente. Y en realidad mi solución fue un poco diferente a la de @ maxim1000.

Este es un archivo de encabezado (muy simplificado) para el enums. En realidad, cada enumclase se definió en su propio archivo. Este archivo representa los archivos de encabezado que se me proporcionan como parte de la biblioteca que estoy usando:

// file enums.h
#include <string>

class Enum1
{
public:
  enum EnumerationItem
  {
    BEARS1,
    BEARS2,
    BEARS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

class Enum2
{
public:
  enum EnumerationItem
  {
    TIGERS1,
    TIGERS2,
    TIGERS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

agregando esta línea solo para separar el siguiente archivo en un bloque de código diferente:

// file TemplateExample.h
#include <string>

template <typename T>
class TemplateExample
{
public:
  TemplateExample(T t);
  virtual ~TemplateExample();

  // this is the function I was most concerned about. Unlike @maxim1000's
  // answer where (s)he declared it outside the class with full template
  // parameters, I was able to keep mine declared in the class just like
  // this
  std::string toString();

private:
  T type_;
};

template <typename T>
TemplateExample<T>::TemplateExample(T t)
  : type_(t)
{

}

template <typename T>
TemplateExample<T>::~TemplateExample()
{

}

siguiente archivo

// file TemplateExample.cpp
#include <string>

#include "enums.h"
#include "TemplateExample.h"

// for each enum type, I specify a different toString method, and the
// correct one gets called when I call it on that type.
template <>
std::string TemplateExample<Enum1::EnumerationItem>::toString()
{
  return Enum1::toString(type_);
}

template <>
std::string TemplateExample<Enum2::EnumerationItem>::toString()
{
  return Enum2::toString(type_);
}

siguiente archivo

// and finally, main.cpp
#include <iostream>
#include "TemplateExample.h"
#include "enums.h"

int main()
{
  TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1);
  TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3);

  std::cout << t1.toString() << std::endl;
  std::cout << t2.toString() << std::endl;

  return 0;
}

y esto produce:

BEARS1
TIGERS3

No tengo idea de si esta es la solución ideal para resolver mi problema, pero funcionó para mí. Ahora, no importa cuántos tipos de enumeración termine usando, todo lo que tengo que hacer es agregar algunas líneas para el toStringmétodo en el archivo .cpp, y puedo usar el toStringmétodo de bibliotecas ya definido sin implementarlo yo mismo y sin extender cada enumclase que quiero usar.

yano
fuente