¿Cómo sobrecargar correctamente el operador << para una ostream?

237

Estoy escribiendo una pequeña biblioteca matricial en C ++ para operaciones matriciales. Sin embargo, mi compilador se queja, donde antes no lo hacía. Este código se dejó en un estante durante 6 meses y en el medio actualicé mi computadora de debian etch a lenny (g ++ (Debian 4.3.2-1.1) 4.3.2) sin embargo, tengo el mismo problema en un sistema Ubuntu con el mismo g ++ .

Aquí está la parte relevante de mi clase de matriz:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix);
    }
}

Y la "implementación":

using namespace Math;

std::ostream& Matrix::operator <<(std::ostream& stream, const Matrix& matrix) {

    [...]

}

Este es el error dado por el compilador:

matrix.cpp: 459: error: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' debe tomar exactamente un argumento

Estoy un poco confundido por este error, pero nuevamente mi C ++ se ha oxidado un poco después de hacer mucho Java esos 6 meses. :-)

Matthias van der Vlies
fuente

Respuestas:

127

Has declarado tu función como friend. No es un miembro de la clase. Debe eliminar Matrix::de la implementación. friendsignifica que la función especificada (que no es miembro de la clase) puede acceder a las variables miembro privadas. La forma en que implementó la función es como un método de instancia para la Matrixclase que está mal.

Mehrdad Afshari
fuente
77
Y también debe declararlo dentro del espacio de nombres Math (no solo con un espacio de nombres usando Math).
David Rodríguez - dribeas
1
¿Por qué operator<<tiene que estar en el espacio de nombres de Math? Parece que debería estar en el espacio de nombres global. Estoy de acuerdo en que mi compilador quiere que esté en el espacio de nombres de Math, pero eso no tiene sentido para mí.
Mark Lakata
Lo siento, pero no veo por qué usamos la palabra clave amigo aquí, entonces. Cuando declaramos la anulación del operador amigo en una clase, parece que no podemos implementarlo con Matrix :: operator << (ostream & os, const Matrix & m). En cambio, solo debemos usar el operador de anulación del operador global << ostream & os, const Matrix & m), entonces, ¿por qué molestarse en declararlo dentro de la clase en primer lugar?
Patrick
139

Solo le cuento sobre otra posibilidad: me gusta usar definiciones de amigos para eso:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix) {
            [...]
        }
    };
}

La función se dirigirá automáticamente al espacio de nombres circundante Math(aunque su definición aparezca dentro del alcance de esa clase) pero no será visible a menos que llame al operador << con un objeto Matrix que hará que la búsqueda dependiente del argumento encuentre esa definición de operador. Eso a veces puede ayudar con llamadas ambiguas, ya que es invisible para los tipos de argumentos distintos de Matrix. Al escribir su definición, también puede referirse directamente a los nombres definidos en Matrix y a la propia Matrix, sin calificar el nombre con algún prefijo posiblemente largo y proporcionar parámetros de plantilla como Math::Matrix<TypeA, N>.

Johannes Schaub - litb
fuente
77

Para agregar a la respuesta de Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


    }   
    std::ostream& operator<< (std::ostream& stream, const Math::Matrix& matrix);
}

En su implementación

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }
kal
fuente
44
No entiendo por qué es un voto negativo, esto aclara que puede declarar que el operador está en el espacio de nombres y ni siquiera como un amigo, y cómo puede declarar el operador.
kal
2
La respuesta de Mehrdad no tenía ningún fragmento de código, así que agregué lo que podría funcionar moviéndolo fuera de la clase en el espacio de nombres.
kal
Entiendo tu punto, solo miré tu segundo fragmento. Pero ahora veo que sacaste al operador de la clase. Gracias por la sugerencia.
Matthias van der Vlies
77
No solo está fuera de la clase, sino que se define correctamente dentro del espacio de nombres matemático. También tiene la ventaja adicional (tal vez no para Matrix, pero con otras clases) de que 'imprimir' puede ser virtual y, por lo tanto, la impresión se realizará en el nivel de herencia más derivado.
David Rodríguez - dribeas
68

Suponiendo que estamos hablando de sobrecargar operator <<para todas las clases derivadas std::ostreampara manejar la Matrixclase (y no sobrecargar <<para la Matrixclase), tiene más sentido declarar la función de sobrecarga fuera del espacio de nombres matemático en el encabezado.

Utilice una función amiga solo si la funcionalidad no se puede lograr a través de las interfaces públicas.

Matrix.h

namespace Math { 
    class Matrix { 
        //...
    };  
}
std::ostream& operator<<(std::ostream&, const Math::Matrix&);

Tenga en cuenta que la sobrecarga del operador se declara fuera del espacio de nombres.

Matrix.cpp

using namespace Math;
using namespace std;

ostream& operator<< (ostream& os, const Matrix& obj) {
    os << obj.getXYZ() << obj.getABC() << '\n';
    return os;
}

Por otro lado, si su función de sobrecarga no es necesario hacer un amigo es decir, necesita tener acceso a los miembros privados y protegidos.

Math.h

namespace Math {
    class Matrix {
        public:
            friend std::ostream& operator<<(std::ostream&, const Matrix&);
    };
}

Debe encerrar la definición de la función con un bloque de espacio de nombres en lugar de solo using namespace Math;.

Matrix.cpp

using namespace Math;
using namespace std;

namespace Math {
    ostream& operator<<(ostream& os, const Matrix& obj) {
        os << obj.XYZ << obj.ABC << '\n';
        return os;
    }                 
}
sanjivr
fuente
38

En C ++ 14 puede usar la siguiente plantilla para imprimir cualquier objeto que tenga un T :: print (std :: ostream &) const; miembro.

template<class T>
auto operator<<(std::ostream& os, T const & t) -> decltype(t.print(os), os) 
{ 
    t.print(os); 
    return os; 
} 

En C ++ 20 se pueden usar conceptos.

template<typename T>
concept Printable = requires(std::ostream& os, T const & t) {
    { t.print(os) };
};

template<Printable T>
std::ostream& operator<<(std::ostream& os, const T& t) { 
    t.print(os); 
    return os; 
} 
QuentinUK
fuente
solución interesante! Una pregunta: ¿dónde debe declararse este operador, como en un ámbito global? Supongo que debería ser visible para todos los tipos que se pueden usar para modelarlo.
barney
@barney Podría estar en su propio espacio de nombres junto con las clases que lo usan.
QuentinUK
¿No puedes simplemente regresar std::ostream&, ya que es el tipo de retorno de todos modos?
Jean-Michaël Celerier
55
@ Jean-MichaëlCelerier El decltype se asegura de que este operador solo se use cuando t :: print está presente. De lo contrario, intentaría compilar el cuerpo de la función y generaría un error de compilación.
QuentinUK
Versión de conceptos agregada, probada aquí godbolt.org/z/u9fGbK
QuentinUK