einpoklum einpoklum - 1 month ago 11
C++ Question

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
ns::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.

Answer

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 (not-inlined)
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';
}