我真的需要为命名空间中的类的朋友运算符<< 向后弯腰吗?

Posted

技术标签:

【中文标题】我真的需要为命名空间中的类的朋友运算符<< 向后弯腰吗?【英文标题】:Do I really need to bend over backwards for a friend operator<< for a class in a namespace? 【发布时间】:2017-03-14 22:43:26 【问题描述】:

我想实现一个运算符Paragraph)。 Paragraph 类有一些私有数据,因此我希望(独立)运算符here on SO。 friend 声明,执行operator&lt;&lt; 一切都很好。

但现在我想将 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&lt;&lt;,而不是::operator&lt;&lt;。好,可以。因此,我将朋友行替换为:

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

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

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&lt; 的声明时不知道Paragraph。好的,让我们向前声明:

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' has not been declared”错误。 (咬牙)好!就这样吧!

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&lt;&lt;不能内联,必须单独定义。

【问题讨论】:

【参考方案1】:

您的问题似乎源于没有意识到ADL 可以找到正确的operator&lt;&lt;,只要它与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';

【讨论】:

你的第一句话一针见血。或者我应该说我的头。 @einpoklum 此外,这是保证名称查找成功的唯一解决方案。 ::operator&lt;&lt; 在某些情况下找不到,因为它不是 ADL 查找集的一部分。【参考方案2】:

只需将&lt;&lt; 运算符的定义与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;
    

【讨论】:

以上是关于我真的需要为命名空间中的类的朋友运算符<< 向后弯腰吗?的主要内容,如果未能解决你的问题,请参考以下文章

命名空间中的类并使用模板类型作为返回类型时的全局范围友元运算符声明

如何在另一个 C++ 命名空间内的全局命名空间中定义朋友?

交友/使用不同命名空间中的类

在C#中获取命名空间中的类列表[重复]

final,类的自动加载,命名空间

VB.Net 中的类命名决策