Bueno usualmente no me gusta extenderme demasiado en las explicaciones al menos que sea realmente necesario, y en este caso lo es. Viendo el código ya el problema ha quedado claro, espero que la explicación también.
Cuando el compilador recorre la clase y la analiza, al encontrarse con la declaración friend de la sobrecarga del operador, en este punto:
template<typename TYPE>class matriz{
protected:
unsigned filas,columnas;
TYPE** datos;
public:
matriz (unsigned FILAS,unsigned COLUMNAS);
friend std::ostream &operator << (std::ostream &os,matriz<TYPE> X); // sobrecarga friend
};
el compilador
no sabe que dichas funciones en si mismas son templates, entonces asume que la definición de dichas funciones son precisamente, no-template.
Al utilizar los métodos propiamente dichos, el compilador genera una llamada a las versiones no-template de dichos métodos, lo cual seria así:
std::ostream &operator << (std::ostream &os,matriz<TYPE> X);// Sobrecarga friend no-template
pero esos métodos nunca están definidos puesto que en realidad técnicamente no existen. Entonces en ese momento el linker te da el error de referencia indefinida.
La forma de solucionarlo es basicamente aclarándole al compilador que la versión de la función que tiene que buscar es precisamente, un template.
Primero que nada, usamos forward declarations de la clase y de la sobrecarga:
template<typename TYPE> class matriz;
template<typename TYPE> std::ostream &operator <<(std::ostream &os,matriz<TYPE> X);
y luego de eso, agregamos <> a la sobrecarga del operador para que el compilador al recorrer la clase sepa que nos estamos refiriendo a un template:
friend std::ostream &operator << <> (std::ostream &os,matriz<TYPE> X);
y luego lo defines:
template<typename TYPE> std::ostream &operator << (std::ostream &os, matriz<TYPE> X)
{
//la logica aqui
}
El código final te debería quedar así:
template<typename TYPE> class matriz; //Forward declaration de la clase
template<typename TYPE> std::ostream &operator <<(std::ostream &os,matriz<TYPE> X); // Forward declaration de la sobrecarga
//Definición de la clase
template<typename TYPE>class matriz{
protected:
unsigned filas,columnas;
TYPE** datos;
public:
matriz (unsigned FILAS,unsigned COLUMNAS);
friend std::ostream &operator << <> (std::ostream &os,matriz<TYPE> X); //Especificar versión template
};
//Definición del constructor
template<typename TYPE> matriz<TYPE>::matriz(unsigned FILAS, unsigned COLUMNAS){
filas=FILAS;
columnas=COLUMNAS;
datos=new TYPE *[filas];
for(unsigned i=0;i<filas;i++){
datos[i]=new TYPE [columnas];
for(unsigned j=0;j<columnas;j++){
datos[i][j]=0;
};
};
};
//Sobrecarga del operador
template<typename TYPE> std::ostream &operator << (std::ostream &os, matriz<TYPE> X){
for(unsigned i=0;i<X.filas;i++){
for(unsigned j=0;j<X.columnas;j++){
os<<X.datos[i][j]<<'\t';
};
os<<'\n';
};
return os;
};
int main(int argc,char* argv[]){
matriz<int> A=matriz<int>(4,4);
std::cout<<A<<std::endl;
std::system("pause");
return 0;
};
Eso o declarar y definir la sobrecarga dentro de la clase.
No lo he probado porque tengo el compilador bastante ocupado, pero si no llega a funcionar correctamente, ya sabes.
Saludos!