Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 608
einpoklum Avatar asked Nov 01 '16 18:11

einpoklum


2 Answers

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';
}
like image 155
Ryan Haining Avatar answered Oct 09 '22 15:10

Ryan Haining


Just put the definition of the << operator in the namespace along with Paragraph. Argument-dependent lookup will find it when you insert a Paragraph object into a stream.

// myheader.h:
namespace ns {
    struct x {
        /* ... */
        friend std::ostream& operator<<(std::ostream&, const x&);
    };
}

// myheader.cpp:
#include "myheader.h"
namespace ns {
    std::ostream& operator<<(std::ostream& os, const x& xx) {
        os << xx.whatever() << '\n';
        return os;
    }
}

Or, if it's small enough to warrant inlining, just do that:

// myheader.h:
namespace ns {
    struct x {
        /* ... */
        friend std::ostream& operator<<(std::ostream&, const x&);
    };
    inline std::ostream& operator<<(std::ostream& os, const x& xx) {
        os << xx.whatever() << '\n';
        return os;
    }
}
like image 32
Pete Becker Avatar answered Oct 09 '22 14:10

Pete Becker