Sobrecarga del operador amigo << para la plantilla de clase

Resuelto starcorn asked hace 13 años • 5 respuestas

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

starcorn avatar Jan 11 '11 23:01 starcorn
Aceptado

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 Testplantilla. 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 Testcon doubleo 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...
   }
}
David Rodríguez - dribeas avatar Jan 11 '2011 18:01 David Rodríguez - dribeas

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;
}
EmeryBerger avatar Jan 11 '2011 16:01 EmeryBerger

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.

Alessandro Teruzzi avatar Jan 11 '2011 17:01 Alessandro Teruzzi