Antes de nada, comentar que este tema ya se ha tratado en esta otra pregunta:
Aconsejo también su lectura ya que tiene respuestas igualmente interesantes.
Tu error es un problema de concepto.
En C++ no hay ningún problema en separar la implementación de la clase de su declaración o interfaz. Esto es así porque es tarea del enlazador el coser el programa final, luego a los consumidores de tu clase solo les importa conocer la interfaz de la misma para calcular la cantidad de memoria que va a consumir en la pila, para concer la firma de las funciones, etc...
Sin embargo las plantillas no son clases al uso. Cuando tu implementas una plantilla:
template<class T>
T func(T t)
{ return t * 2; }
El compilador no genera código fuente alguno. Cuando el compilador se encuentra una plantilla se limita a apuntarla en una lista y sigue su camino... es cuando se encuentra usos de la misma cuando empieza a compilar una plantilla.
¿Y a qué viene este comportamiento tan extraño?
A que una plantilla, como tal, no tiene código válido. En el ejemplo anterior, ¿qué es T
exactamente? ¿Cómo sabemos que se puede multiplicar por dos? ¿Y cómo es ese proceso exactamente? ¿Se pueden hacer copias de T
? No tendremos respuestas hasta que no sepamos qué tipo concreto de dato es T
.
Si nosotros ahora hacemos lo siguiente:
int main()
{
std::cout << func(10);
}
Veremos que el programa imprime 20
, lo cual es correcto... pero si hacemos esto otro:
int main()
{
std::cout << func(10) << func("ABC");
}
Obtendremos un bonito error de compilación que dice que no sabe cómo usar el operador *
entre un const char*
y un int
. El error no ha salido hasta que no hemos puesto un uso inválido de la plantilla, lo cual demuestra que no se ha generado código a partir de la plantilla hasta que no la intentamos usar.
Pues bien, resulta además que el compilador de C++, para poder compilar cualquier cosa, una clase, una función, una plantilla... necesita tener su implementación disponible... y aquí tenemos el origen de tu problema. Tu has separado la declaración de la implementación y, a la hora de usar la plantilla, el compilador solo dispone de la declaración, por lo que no puede generar el código correspondiente. Luego llega el enlazador y se da cuenta de que las funciones que tiene que usar no están disponibles y se genera el error que te sale a ti.
Para evitar problemas acostumbra a dejar la declaración y la implementación de las plantillas en un mismo fichero:
#ifndef nodo_h
#define nodo_h
#include <iostream>
template<class type>
class nodo
{
private:
type element;
nodo *next, *previous;
public:
void SetElement(type element);
void ShowElement();
type GetElement();
nodo* GetNext();
nodo* GetPrevious();
nodo();
~nodo();
};
template <class type>
nodo<type>::nodo()
{
next = NULL;
previous = NULL;
}
template <class type>
nodo<type>::~nodo()
{
}
template <class type>
void nodo<type>::SetElement(type element)
{
this->element = element;
}
template <class type>
void nodo<type>::ShowElement()
{
std::cout << element << endl;
}
template <class type>
type nodo<type>::GetElement()
{
return element;
}
template <class type>
nodo<type>* nodo<type>::GetNext()
{
return next;
}
template <class type>
nodo<type>* nodo<type>::GetPrevious()
{
return previous;
}
#endif
Nota también que, en GetNext
y GetPrevious
te falta indicar la especialización del tipo de retorno:
template <class type>
nodo<type>* nodo<type>::GetNext()
// ^^^^^^
{
return next;
}
Si no lo haces el compilador no sabrá cual es la especialización de la plantilla que se devuelve y te generará un error (error que ahora mismo no te sale porque, como has podido ver, no se están compilando las funciones)
Y sí, esto es necesario porque una plantilla puede devolver lo que sea y el compilador no es quien para decidir por ti.
Espera... ¿Y por qué no hace falta actualizar también la cabecera? El código compila y la declaración sigue estando mal:
template<class type>
class nodo
{
private:
type element;
nodo *next, *previous;
public:
void SetElement(type element);
void ShowElement();
type GetElement();
nodo* GetNext(); // <<--- ¿¿??
nodo* GetPrevious(); // <<--- ¿¿??
nodo();
~nodo();
};
Aquí no hay problema y es por cómo funciona el lenguaje. Los usos de nodo
que el compilador se encuentre en la declaración, si no tienen una especialización explícita, se sobreentiende que se especializan para el tipo type
. Es decir, la declaración actual es equivalente a la siguiente:
template<class type>
class nodo
{
private:
type element;
nodo *next, *previous;
public:
void SetElement(type element);
void ShowElement();
type GetElement();
nodo<type>* GetNext();
nodo<type>* GetPrevious();
nodo<type>();
~nodo<type>();
};
Y, por cierto, procura no usar using namespace
en las cabeceras. Para más información consulta esta otra pregunta: