[C con Clase] problema sobrecargando << hacia stringstream

Steven Davidson steven en conclase.net
Lun Mar 26 17:27:43 CEST 2007


Hola Pedro,

El pasado 2007-03-26 04:30:08, Pedro Mateo escribió:

PM> /*
PM>     saludo
PM>     he creado una plantilla para poder sobrecargar << de mi clase a
PM> cualquier otra
PM>     esto funciona para cout pero no para stringstream
PM>     en el siguiente codigo me explico mejor
PM> */
PM> #include <iostream>
PM> #include <sstream>
PM> using namespace std;
PM> class miclase{
PM>        public:
PM>        template <typename T> friend
PM>        T& operator<<(T& o, const miclase& x)     {  return o<<"esta es
PM> miclase";}
PM> };
PM> main(){
PM>     miclase x;
PM>     cout<<x<<endl;  //esto me sale bien
PM>     //   hasta aqui si no agrega las siguientes lineas el programa se
PM> compila sin errores
PM>     //   puede comentar las siguientes lineas para verificar lo que digo
PM>       stringstream c;
PM>     c<<x;
PM>     cout<<c.str()<<endl;
PM>         //     el punto es que la plantilla que funciona en cout<<x<<endl;
PM>         //     entiendo que deberia funcionarme en c<<x;
PM>         //     me da el siguiente error al compilar
PM>         /*
PM>                 test.cpp: In function 'T& operator<<(T&, const miclase&)
PM> [with T = std::stringstream]':
PM>                 test.cpp:23:   instantiated from here
PM>                 test.cpp:12: error: inicialización inválida de la referencia
PM> de tipo 'std::stringstream&' desde una expresión de                     tipo
PM> 'std::basic_ostream<char, std::char_traits<char> >'
PM>         */
PM> }

El error está en la función. Escribes:

template <typename T>
T& operator<<( T& o, const miclase& x )
{
  return o << "esta es miclase";
}

El problema es que la clase 'stringstream' no tiene sobrecargada ningún operador. En este caso, no existe la siguiente definición:

stringstream & operator<<( stringstream &, const char * );

Pero sí existe la siguiente:
ostream & operator<<( ostream &, const char * );

Como la clase 'stringstream' hereda de 'iostream' y ésta hereda de 'istream' y 'ostream', entonces podemos usar objetos de 'stringstream' en cualesquier funciones y operadores sobrecargados de 'ostream' e 'istream. Esto es,

stringstream ss;

ss << "hola";

Realmente, acabamos por hacer lo siguiente:

ostream &os = ss;
os << "hola";

Es decir, terminamos por invocar el operador sobrecargado << de 'ostream'. Esto implica que se retornará una referencia a un objeto de tipo 'ostream', ya que usamos el operador sobrecargado para 'ostream'.

Dicho lo anterior, el compilador nos muestra el error, ya que no hay posibilidad de convertir un objeto 'ostream' a 'stringstream'. Volviendo a tu función-plantilla:

template <typename T>
T& operator<<( T& o, const miclase& x )
{
  return o << "esta es miclase";
}

vemos el error al usar 'stringstream', ya que se generará este operador sobrecargado:

stringstream& operator<<( stringstream& o, const miclase& x )
{
  return o << "esta es miclase";
}

Sin embargo, como ya he explicado, usamos el operador << de 'ostream'. Al final, la sobrecarga anterior termina por retornar una referencia a un objeto 'ostream', mientras que tu definición intenta retornar una referencia a un objeto 'stringstream'. O sea, el comportamiento es el siguiente:

stringstream& operator<<( stringstream& o, const miclase& x )
{
  ostream &__temp = o;
  __temp << "esta es miclase"; // ostream &operator<<( ostream &, const char * )
  stringstream &__ret = __temp;  // <--- ERROR
  return __ret;
}

El error está en que no existe la conversión de 'ostream' a 'stringstream'.

Tenemos dos soluciones que podemos implementar:

1. Reescribir la definición de la plantilla para retornar la referencia correcta. Esto es,

template <typename T>
T& operator<<( T& o, const miclase& x )
{
  o << "esta es miclase";
  return o;
}

2. No usar el parámetro T tan genéricamente. O sea, restrinjamos el tipo T. Sugiero reescribir la definición de la plantilla de esta manera:

template <typename T>
basic_ostream<T>& operator<<( basic_ostream<T>& o, const miclase& x )
{
  return o << "esta es miclase";
}

De esta manera, cualquier objeto (o referencia) de la clase-plantilla 'basic_ostream' puede tomar un objeto de la clase 'miclase'. Esto significa que cualquier objeto de la clase base o sus derivadas pueden hacer uso de este operador. En este caso, el parámetro T de la plantilla hace alusión al tipo de los elementos del canal que típicamente será 'char' o 'wchar_t'.

Supongo que también puedes optar por definir ambas plantillas. Por ejemplo,

template <typename T>
T& operator<<( T& o, const miclase& x )
{
  // Hacer algo aquí
}

template <typename T>
basic_ostream<T>& operator<<( basic_ostream<T>& o, const miclase& x )
{
  return o << "esta es miclase";
}

En este caso, definimos un operador general 'T' y otro especializado 'basic_ostream<T>'. Con la primera sobrecarga, podemos usar otros tipos de objetos, como por ejemplo,

class MiOtraClase {...};
...
miclase x;
MiOtraClase moc;

moc << x;


Espero haber aclarado la duda.

Steven


Más información sobre la lista de distribución Cconclase