Sobrecarga del operador amigo << para la plantilla de clase
La sobrecarga <<
funciona si la convierto en una función en línea. ¿Pero cómo hago para que funcione en mi caso?
#include <iostream>
using namespace std;
template <class T>
T my_max(T a, T b) {
if(a > b) return a;
else return b;
}
template <class classT>
class D {
public:
D(classT in) : d(in) {};
bool operator>(const D& rhs) const;
classT operator=(const D<classT>& rhs);
friend ostream& operator<<(ostream &os, const D<classT>& rhs);
private:
classT d;
};
template <class classT>
ostream& operator<<(ostream &os, const D<classT>& rhs) {
os << rhs.d;
return os;
}
int main() {
D<int> d1(1);
D<int> d2(2);
cout << my_max(d1, d2) << endl;
}
Esto no se puede compilar:
warning: friend declaration std::ostream& operator<<(std::ostream&, const D<classT>&)' declares a non-template function
warning: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) -Wno-non-template-friend disables this warning
/tmp/cc6VTWdv.o:uppgift4.cc:(.text+0x180): undefined reference to operator<<(std::basic_ostream<char, std::char_traits<char> >&, D<int> const&)' collect2: ld returned 1 exit status
Esta es una de esas preguntas frecuentes que tienen diferentes enfoques que son similares pero no iguales. Los tres enfoques difieren en quién usted declara amigo de su función y luego en cómo lo implementa.
el extrovertido
Declare todas las instancias de la plantilla como amigas. Esto es lo que ha aceptado como respuesta y también lo que proponen la mayoría de las otras respuestas. En este enfoque, estás abriendo innecesariamente tu creación de instancias particular D<T>
al declarar amigas todas operator<<
las instancias. Es decir, std::ostream& operator<<( std::ostream &, const D<int>& )
tiene acceso a todas las partes internas de D<double>
.
template <typename T>
class Test {
template <typename U> // all instantiations of this template are my friends
friend std::ostream& operator<<( std::ostream&, const Test<U>& );
};
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& ) {
// Can access all Test<int>, Test<double>... regardless of what T is
}
los introvertidos
Declare únicamente una instancia particular del operador de inserción como amigo. D<int>
Puede que le guste el operador de inserción cuando se aplica a sí mismo, pero no quiere tener nada que ver con std::ostream& operator<<( std::ostream&, const D<double>& )
.
Esto se puede hacer de dos maneras, la forma más sencilla es la propuesta por @Emery Berger, que consiste en incluir al operador, lo cual también es una buena idea por otras razones:
template <typename T>
class Test {
friend std::ostream& operator<<( std::ostream& o, const Test& t ) {
// can access the enclosing Test. If T is int, it cannot access Test<double>
}
};
En esta primera versión, no está creando una función con plantilla operator<<
, sino una función sin plantilla para cada instancia de la Test
plantilla. Nuevamente, la diferencia es sutil, pero esto es básicamente equivalente a agregar manualmente: std::ostream& operator<<( std::ostream&, const Test<int>& )
cuando creas una instancia Test<int>
y otra sobrecarga similar cuando creas una instancia Test
con double
o con cualquier otro tipo.
La tercera versión es más complicada. Sin insertar el código y con el uso de una plantilla, puedes declarar una única instancia de la plantilla como amiga de tu clase, sin abrirte a todas las demás instancias:
// Forward declare both templates:
template <typename T> class Test;
template <typename T> std::ostream& operator<<( std::ostream&, const Test<T>& );
// Declare the actual templates:
template <typename T>
class Test {
friend std::ostream& operator<< <T>( std::ostream&, const Test<T>& );
};
// Implement the operator
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& t ) {
// Can only access Test<T> for the same T as is instantiating, that is:
// if T is int, this template cannot access Test<double>, Test<char> ...
}
Aprovechando al extrovertido
La sutil diferencia entre esta tercera opción y la primera es cuánto te abres a otras clases. Un ejemplo de abuso en la versión extrovertida sería alguien que quiere acceder a tus entrañas y hace esto:
namespace hacker {
struct unique {}; // Create a new unique type to avoid breaking ODR
template <>
std::ostream& operator<< <unique>( std::ostream&, const Test<unique>& )
{
// if Test<T> is an extrovert, I can access and modify *any* Test<T>!!!
// if Test<T> is an introvert, then I can only mess up with Test<unique>
// which is just not so much fun...
}
}
Esto funcionó para mí sin ninguna advertencia del compilador.
#include <iostream>
using namespace std;
template <class T>
T my_max(T a, T b)
{
if(a > b)
return a;
else
return b;
}
template <class classT>
class D
{
public:
D(classT in)
: d(in) {};
bool operator>(const D& rhs) const {
return (d > rhs.d);
}
classT operator=(const D<classT>& rhs);
friend ostream& operator<< (ostream & os, const D& rhs) {
os << rhs.d;
return os;
}
private:
classT d;
};
int main()
{
int i1 = 1;
int i2 = 2;
D<int> d1(i1);
D<int> d2(i2);
cout << my_max(d1,d2) << endl;
return 0;
}
Creo que no deberías hacer amigos en primer lugar.
Puede crear una llamada al método público print, algo como esto (para una clase que no sea de plantilla):
std::ostream& MyClass::print(std::ostream& os) const
{
os << "Private One" << privateOne_ << endl;
os << "Private Two" << privateTwo_ << endl;
os.flush();
return os;
}
y luego, fuera de la clase (pero en el mismo espacio de nombres)
std::ostream& operator<<(std::ostream& os, const MyClass& myClass)
{
return myClass.print(os);
}
Creo que debería funcionar también para la clase de plantilla, pero aún no lo he probado.