Search code examples
c++privateencapsulationfriend

Do I really need to bend over backwards for a friend operator<< for a class in a namespace?


I want to implemnent an operator<< for streaming my class (say it's named Paragraph). Class Paragraph has some private data, for which reason I want the (freestanding) operator<< function to be a friend. So, I do as suggested, e.g., here on SO. friend statement, implement the operator<< and all is well.

But now I want to put Paragraph inside a namespace, say namespace foo. It no longer works! If I write:

namespace foo {
class Paragraph {
    public:
        explicit Paragraph(std::string const& init) :m_para(init) {}
        std::string const&  to_str() const { return m_para; }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};
} // namespace foo

The compiler tells me I've befriended foo::operator<<, not ::operator<<. Ok, fair enough. So, I replace the friend line with:

    friend std::ostream & ::operator<<(std::ostream &os, const Paragraph& p);

but I get an error again (from GCC 5.4.0), telling me the ::operator<< has not having been declared. Ok, let's declare it then. Will this work?:

namespace foo {
std::ostream & ::operator<<(std::ostream &os, const foo::Paragraph& p);
class Paragraph {
    public:
        explicit Paragraph(std::string const& init) :m_para(init) {}
        std::string const&  to_str() const { return m_para; }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};
} // namespace foo

Nope, it doesn't know about Paragraph when reading the declaration of ::operator<. Ok, let's forward-declare:

namespace foo {
class Paragraph;
std::ostream & ::operator<<(std::ostream &os, const foo::Paragraph& p);
class Paragraph { /* actual definition here */ } }
std::ostream & operator<<(std::ostream &os, const foo::Paragraph& p) { /* impl */ }

no luck. I get the weird error:

f.cpp:23:16: note: candidate: std::ostream& operator<<(std::ostream&, const foo::Paragraph&)
 std::ostream & operator<<(std::ostream &os, const foo::Paragraph& p)
                ^
f.cpp:11:15: note: candidate: std::ostream& operator<<(std::ostream&, const foo::Paragraph&)
 std::ostream& ::operator<<(std::ostream &os, const foo::Paragraph& p);

... and here I was thinking the global namespace and the :: namespace are the same thing... arent' they? (sigh). No matter, let's move the declaration out of the namespace:

class foo::Paragraph;
std::ostream & operator<<(std::ostream &os, const foo::Paragraph& p);
namespace foo { class Paragraph { /* actual definition here */ } }
std::ostream & operator<<(std::ostream &os, const foo::Paragraph& p) { /* impl */ }

Still not good enough, now I get the "'foo' has not been declared" error. (granshes teeth) fine! Be that way!

namespace foo { class Paragraph; }
std::ostream & operator<<(std::ostream &os, const foo::Paragraph& p);

namespace foo { class Paragraph { /* actual definition here */ } }
std::ostream & operator<<(std::ostream &os, const foo::Paragraph& p) { /* impl */ }

so this, but no less than this, works. That's terrible! Surely there must be some kind of less verbose way to do it... right?

Note: Assume the operator<< can't be inlined and must be defined separately.


Solution

  • It seems your problem stems from not realizing how ADL can find the right operator<< as long as it is the same namespace as Paragraph. To expand on your first example

    // this is what you have already
    namespace foo {
    class Paragraph {
     public:
      explicit Paragraph(std::string const& init) :m_para(init) {}
      std::string const&  to_str() const { return m_para; }
     private:
      friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
      std::string     m_para;
    };
    } // namespace foo
    
    // Now we can add a definition here, or in a different TU
    namespace foo {
    std::ostream& operator<<(std::ostream& os, const Paragraph& p) {
      return os << p.m_para;
    }
    } // namespace foo
    
    
    int main() {
      foo::Paragraph p("hello");
      // finds your operator<< using adl
      std::cout << p << '\n';
    }