Como sobrecarregar corretamente o << operador para um ostream?

237

Estou escrevendo uma pequena biblioteca de matrizes em C ++ para operações de matriz. No entanto, meu compilador reclama, onde antes não o fazia. Este código foi deixado em uma prateleira por 6 meses e entrei atualizei meu computador do debian etch para o lenny (g ++ (Debian 4.3.2-1.1) 4.3.2), no entanto, tenho o mesmo problema em um sistema Ubuntu com o mesmo g ++ .

Aqui está a parte relevante da minha classe de matriz:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

E a "implementação":

using namespace Math;

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

    [...]

}

Este é o erro fornecido pelo compilador:

matrix.cpp: 459: erro: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' deve ter exatamente um argumento

Estou um pouco confuso com esse erro, mas, novamente, meu C ++ ficou um pouco enferrujado depois de fazer muito Java nesses 6 meses. :-)

Matthias van der Vlies
fonte

Respostas:

127

Você declarou sua função como friend. Não é um membro da classe. Você deve remover Matrix::da implementação. friendsignifica que a função especificada (que não é membro da classe) pode acessar variáveis ​​de membro privadas. A maneira como você implementou a função é como um método de instância para a Matrixclasse que está errada.

Mehrdad Afshari
fonte
7
E você também deve declará-lo dentro do namespace Math (não apenas com um namespace usando Math).
David Rodríguez - dribeas 23/03/09
1
Por que operator<<tem que estar no espaço para nome de Math? Parece que deveria estar no espaço de nomes global. Concordo que meu compilador deseja que ele esteja no espaço de nomes de Math, mas isso não faz sentido para mim.
Mark Lakata
Desculpe, mas não consigo entender por que usamos a palavra-chave friend aqui? Quando declarar que um operador amigo substitui uma classe, parece que não podemos implementar com Matrix :: operator << (ostream & os, const Matrix & m). Em vez disso, precisamos usar apenas o operador global de substituição de operador << ostream & os, const Matrix & m).
Patrick
139

Apenas falando sobre uma outra possibilidade: eu gosto de usar definições de amigos para isso:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

A função será automaticamente direcionada para o espaço para nome circundante Math(mesmo que sua definição apareça no escopo dessa classe), mas não estará visível a menos que você chame o operador << com um objeto Matrix que fará com que a pesquisa dependente de argumento encontre essa definição de operador. Às vezes, isso pode ajudar com chamadas ambíguas, pois é invisível para outros tipos de argumento que não sejam Matrix. Ao escrever sua definição, você também pode se referir diretamente aos nomes definidos em Matrix e à própria Matrix, sem qualificar o nome com algum prefixo possivelmente longo e fornecer parâmetros de modelo como Math::Matrix<TypeA, N>.

Johannes Schaub - litb
fonte
77

Para adicionar à resposta Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


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

Na sua implementação

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }
kal
fonte
4
Não entendo por que isso é um voto negativo, isso esclarece que você pode declarar que o operador está no espaço para nome e nem mesmo como amigo e como você pode declarar o operador.
kal
2
A resposta da Mehrdad não tinha nenhum trecho de código, então acabei de adicionar o que poderia funcionar movendo-o para fora da classe no próprio espaço para nome.
kal
Entendo o seu ponto, só olhei para o seu segundo trecho. Mas agora vejo que você tirou o operador da sala. Obrigado pela sugestão.
Matthias van der Vlies
7
Ele não está apenas fora da classe, mas é definido corretamente dentro do espaço para nome Math. Além disso, possui a vantagem adicional (talvez não para uma Matrix, mas com outras classes) de que 'print' pode ser virtual e, portanto, a impressão ocorrerá no nível de herança mais derivado.
David Rodríguez - dribeas 23/03/09
68

Supondo que estamos falando de sobrecarga operator <<para todas as classes derivadas std::ostreampara manipular a Matrixclasse (e não sobrecarregar <<paraMatrix classe), faz mais sentido declarar a função de sobrecarga fora do espaço de nomes Math no cabeçalho.

Use uma função de amigo apenas se a funcionalidade não puder ser alcançada através das interfaces públicas.

Matrix.h

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

Observe que a sobrecarga do operador é declarada fora do espaço para nome.

Matrix.cpp

using namespace Math;
using namespace std;

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

Por outro lado, se sua função de sobrecarga não precisam ser feitas para um amigo ou seja, precisa de acesso a membros privados e protegidos.

Math.h

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

Você precisa incluir a definição da função com um bloco de espaço para nome em vez de apenas 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
fonte
38

No C ++ 14, você pode usar o seguinte modelo para imprimir qualquer objeto que tenha uma const T :: print (std :: ostream &); membro.

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

No C ++ 20 podem ser utilizados conceitos.

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
fonte
solução interessante! Uma pergunta - onde esse operador deve ser declarado, como em um escopo global? Suponho que deve ser visível para todos os tipos que podem ser usados ​​para templatize-lo?
1613 barney
@ barney Poderia estar no seu próprio espaço para nome, juntamente com as classes que o utilizam.
QuentinUK
você não pode simplesmente retornar std::ostream&, já que é o tipo de retorno?
Jean-Michaël Celerier 8/17
5
@ Jean-MichaëlCelerier O decltype garante que este operador seja usado apenas quando t :: print estiver presente. Caso contrário, ele tentaria compilar o corpo da função e forneceria um erro de compilação.
QuentinUK
Versão de conceitos adicionada, testada aqui godbolt.org/z/u9fGbK
QuentinUK