我真的需要为朋友 operator<< 为命名空间中的 class 竭尽全力吗?

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

我想实现一个运算符<<来流式传输我的 class(比如说它的名字是 Paragraph)。 Class Paragraph 有一些私有数据,因此我希望(独立的)operator<< 函数成为朋友。所以,我按照建议去做,例如 here on SOfriend 语句,执行 operator<< 一切正常。

但现在我想将 Paragraph 放在命名空间中,比如 namespace foo。它不再有效!如果我写:

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

编译器告诉我我是 foo::operator<<,而不是 ::operator<<。好,可以。因此,我将朋友行替换为:

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

但我再次收到错误消息(来自 GCC 5.4.0),告诉我 ::operator<< 尚未声明。好的,那我们就声明一下吧。这行得通吗?:

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

不,它在阅读::operator<的声明时不知道段落。好的,让我们转发声明:

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 */ }

运气不好。我收到奇怪的错误:

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);

... 在这里我在想全局命名空间和 :: 命名空间是同一回事...不是吗? (叹)。不管了,我们把声明移出命名空间:

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 */ }

仍然不够好,现在我收到“'foo' 尚未声明”错误。 (咬牙切齿)好!就这样吧!

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 */ }

所以这个,但不少于这个,有效。那是可怕的!肯定有某种不那么冗长的方法来做到这一点......对吗?

注:假设operator<<不能内联,必须单独定义。

只需将 << 运算符的定义与 Paragraph 一起放在命名空间中。当您将 Paragraph 对象插入流中时,依赖于参数的查找将找到它。

// 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;
    }
}

或者,如果它小到需要内联,就这样做:

// 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;
    }
}

您的问题似乎源于没有意识到 ADL 如何找到正确的 operator<<,只要它与 Paragraph 的名称空间相同。扩展你的第一个例子

// 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';
}