Sobrecarga del operador amigo << para la clase de plantilla

He leído un par de preguntas sobre mi problema en stackoverflow ahora, y nada parece resolver mi problema. O tal vez lo he hecho mal … El << sobrecargado << si lo hago en una función en línea. ¿Pero cómo hago que funcione en mi caso?

warning: friend declaration std::ostream& operator<<(std::ostream&, const D&)' 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 >&, D const&)' collect2: ld returned 1 exit status

El código:

 template  T my_max(T a, T b) { if(a > b) return a; else return b; } template  class D { public: D(classT in) : d(in) {}; bool operator>(const D& rhs) const; classT operator=(const D& rhs); friend ostream& operator<< (ostream & os, const D& rhs); private: classT d; }; int main() { int i1 = 1; int i2 = 2; D d1(i1); D d2(i2); cout << my_max(d1,d2) << endl; return 0; } template  ostream& operator<<(ostream &os, const D& rhs) { os << rhs.d; return os; } 

Esta es una de esas preguntas frecuentes que tienen diferentes enfoques que son similares pero que no son lo mismo. Los tres enfoques difieren en quién declara ser un amigo de su función, y luego en cómo lo implementa.

El extrovertido

Declare todas las instancias de la plantilla como amigos. Esto es lo que ha aceptado como respuesta, y también lo que la mayoría de las otras respuestas proponen. En este enfoque, usted está innecesariamente abriendo su instanciación particular D al declarar amigos a todos los operator<< instanciaciones. Es decir, std::ostream& operator<<( std::ostream &, const D& ) tiene acceso a todas las std::ostream& operator<<( std::ostream &, const D& ) internas de D .

 template  class Test { template  // all instantiations of this template are my friends friend std::ostream& operator<<( std::ostream&, const Test& ); }; template  std::ostream& operator<<( std::ostream& o, const Test& ) { // Can access all Test, Test... regardless of what T is } 

Los introvertidos

Solo declare una instancia particular del operador de inserción como un amigo. D puede gustar 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& ) .

Esto se puede hacer de dos maneras, de la manera más simple, como lo propuso @Emery Berger, que está incrustando al operador, lo que también es una buena idea por otros motivos:

 template  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 } }; 

En esta primera versión, no está creando un operator<< plantilla operator<< , sino una función sin plantilla para cada instancia de la plantilla de Test . De nuevo, la diferencia es sutil, pero esto es básicamente equivalente a agregar manualmente: std::ostream& operator<<( std::ostream&, const Test& ) cuando crea una instancia de Test , y otra sobrecarga similar cuando crea una instancia Test con double o con cualquier otro tipo.

La tercera versión es más engorrosa. Sin incluir el código, y con el uso de una plantilla, puede declarar una sola instanciación de la plantilla como un amigo de su clase, sin abrirse a todas las demás instancias:

 // Forward declare both templates: template  class Test; template  std::ostream& operator<<( std::ostream&, const Test& ); // Declare the actual templates: template  class Test { friend std::ostream& operator<< ( std::ostream&, const Test& ); }; // Implement the operator template  std::ostream& operator<<( std::ostream& o, const Test& t ) { // Can only access Test for the same T as is instantiating, that is: // if T is int, this template cannot access Test, Test ... } 

Aprovechando el extrovertido

La sutil diferencia entre esta tercera opción y la primera es en cuánto estás abriendo a otras clases. Un ejemplo de abuso en la versión extrovertida sería alguien que quiere tener acceso a su interior y hace esto:

 namespace hacker { struct unique {}; // Create a new unique type to avoid breaking ODR template <> std::ostream& operator<< ( std::ostream&, const Test& ) { // if Test is an extrovert, I can access and modify *any* Test!!! // if Test is an introvert, then I can only mess up with Test // which is just not so much fun... } } 

No puede declarar a un amigo así, necesita especificar un tipo de plantilla diferente para él.

 template  friend ostream& operator<< (ostream & os, const D& rhs); 

nota SclassT para que no classT . Al definir

 template  ostream& operator<< (ostream & os, const D& rhs) { // body.. } 

Esto funcionó para mí sin advertencias del comstackdor.

 #include  using namespace std; template  T my_max(T a, T b) { if(a > b) return a; else return b; } template  class D { public: D(classT in) : d(in) {}; bool operator>(const D& rhs) const { return (d > rhs.d); } classT operator=(const D& 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 d1(i1); D d2(i2); cout << my_max(d1,d2) << endl; return 0; } 

Aqui tienes:

 #include  #include  using namespace std; template  T my_max(T a, T b) { if(a > b) return a; else return b; } template  class D { public: D(classT in) : d(in) {}; bool operator>(const D& rhs) const { return d > rhs.d;}; classT operator=(const D& rhs); template friend ostream& operator<< (ostream & os, const D& rhs); private: classT d; }; template ostream& operator<<(ostream& os, class D const& rhs) { os << rhs.d; return os; } int main() { int i1 = 1; int i2 = 2; D d1(i1); D d2(i2); cout << my_max(d1,d2) << endl; return 0; } 

Creo que no deberías hacer amigo en primer lugar.

Puede crear una llamada a 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 todavía no lo he probado.