【问题标题】:Stream operator overloading in nested namespace嵌套命名空间中的流运算符重载
【发布时间】:2013-04-02 15:13:50
【问题描述】:

最近,当我实现一个类时,我创建了一个名为 operators 的嵌套命名空间,我在其中添加了流运算符。

我这样做是因为我经常需要在类的命名空间之外的命名空间中使用它们,我这样做了

using my_namespace::operators;

就在我想要的地方,就是这样。

这里我有一个示例,其中包含 Point 类、Segment 类及其流运算符,其中 Segment 的流调用 Point 的流。但是...我无法编译:

课点:

#ifndef POINT_HPP
#define POINT_HPP

#include <iostream>

namespace geom {

class Point
{
public:
    Point(int x_, int y_) : x(x_), y(y_) {};
    int x;
    int y;
 };

namespace operators {
    std::ostream& operator<<(std::ostream& out, const Point& p)
    {
        out << "(" << p.x << ", " << p.y << ")";
        return out;
    }
} // ~ namespace geom::operators
} // ~ namespace geom

#endif // ~ POINT_HPP

类段:

#ifndef SEGMENT_HPP
#define SEGMENT_HPP

#include <iostream>
#include "point.hpp"

namespace geom_2d {

class Segment
{
public:
    Segment(const geom::Point& a_, const geom::Point& b_) : a(a_), b(b_) {};
    geom::Point a;
    geom::Point b;
};

namespace operators {
    std::ostream& operator<<(std::ostream& out, const Segment& p)
    {
        using namespace geom::operators;
        out << "[" << p.a << ", " << p.b << "]";
        return out;
    }

} // ~ namespace geom_2d::operators
} // ~ namespace geom_2d

#endif // ~ SEGMENT_HPP

主要:

#include <iostream>
#include "segment.hpp"
#include "point.hpp"

using namespace geom_2d::operators;

int main()
{
    geom::Point p1(3, 5);
    geom::Point p2(1, 6);
    geom_2d::Segment s(p1, p2);

    std::cout << s << std::endl;

    return 0;
}

这无法编译,我得到:

../segment.hpp:21: error: no match for ‘operator<<’ in ‘std::operator<< [with _Traits = std::char_traits<char>](((std::basic_ostream<char, std::char_traits<char> >&)((std::ostream*)out)), ((const char*)"[")) << p->geom_2d::Segment::a’

如果我删除命名空间运算符编译正确,但正如我告诉你的,我想避免它。

我认为问题与在另一个命名空间运算符中使用命名空间运算符进行调用有关。

有什么想法吗?

【问题讨论】:

  • 很遗憾没有人真正解释为什么这不能编译。我观察到用using geom_2d::operators::operator&lt;&lt; 替换using namespace geom_2d::operators 和用using namespace geom::operators; 替换using geom::operators::operator&lt;&lt;; 编译并且工作正常。我怀疑 @Dimitris 示例由于运算符重载解析过程而无法编译。

标签: c++ namespaces operator-overloading stream-operators


【解决方案1】:

不清楚为什么您希望运算符与您的类型存在于不同的命名空间中。一般来说,建议操作员应该与他们操作的用户定义类型位于相同的命名空间中。这样做会启用 Argument Dependent Lookup,这反过来将有助于在您使用它时找到正确的运算符(并解决您的编译错误)。

如果有真正理由将运算符放在不同的命名空间中,您可以在该命名空间中提供标记类型,然后使用继承来强制 ADL 查看嵌套的命名空间(使用-directive 对 ADL 没有帮助):

namespace A {
   namespace operators {
      struct tag {};
   }
   struct B : operators::tag {};
   namespace operators {
      std::ostream& operator<<(std::ostream& out, const ::A::B& obj) { ... }
   }
}
namespace C {
   void foo() {
      ::A::B b;
      std::cout << b;
   }
}

请注意,这在某种程度上是一种 hack,有些人会惊讶于运算符未在 A 命名空间中定义......它之所以有效,是因为一个类型的关联命名空间集包括该类型所在的命名空间定义了所有基础的命名空间(在这种情况下,由于::A::B::A::operators::tag 之间的继承关系,::A::operators 被拉出)

注意:如果您在与类型相同的命名空间中定义运算符,那么您根本不需要使用指令,因为 ADL 会找到它们需要时。

【讨论】:

  • 谢谢大卫。我曾经在同一个命名空间中实现重载运算符,但我记得有一次 ADL 找不到它,所以我从 Boost 库中借用了这个想法(虽然不记得是哪个)。我试图在一个简单的案例中进行复制,但我还没有运气。如果我仍然不能,我将从我的大型项目中删除操作符命名空间,看看我是否再次遇到问题。您的带有标签的解决方案将是我最后的选择。
  • 好吧,我懒得花更多时间来复制。我删除了运营商命名空间,它可以工作。您的解决方案也有效。感谢您强调标准方式应该可行
  • @DimitrisDakopoulos:使用标签类型的继承并不是什么新鲜事,这可能是他们在 boost 中所做的。它可以用来避免样板代码,不是针对operator&lt;&lt;,而是针对其他可以用不同的操作符来实现的操作符。例如,考虑operator&lt; 作为operator&gt;operator&lt;=operator&gt;= 的生成器。您可以在您的命名空间中实现operator&lt;,并从您定义template &lt;typename T&gt; bool operator&gt;(T const &amp; a, T const &amp;b) { return b&lt;a; }xxx::operators 命名空间中的类型继承[...]
  • [...] 然后对于您的类型,您可以实现namespace Y { struct A : xxx::operators::tag {...}; bool operator&lt;(A const&amp;, A const&amp;) {...} },其余的&gt;&lt;=&gt;= 是免费的。在这种情况下,您需要将 very generic 模板隐藏在命名空间中,以便它们不在大多数类型的范围内,以避免与用户定义的 operator&gt;=... 和从 @ 的继承发生冲突987654339@(在您的类型中明确请求)确保可以通过 ADL 找到通用实现。
  • [...] 现在,在 operator&lt;&lt; 的特定情况下,它没有意义,因为您 需要 为您的类型手动提供实现(不能从任何其他实现推断,除非你破解它——如果你真的想要,你可以提供一个服务于operator&lt;&lt;operator&gt;&gt;的函数......通常不值得,因为operator&gt;&gt;不常用)
【解决方案2】:

问题是您已经通过using 将命名空间导入到您的代码中,但库头文件并没有这样做。因此他们只能在全局命名空间、命名空间std 或通过参数相关查找中找到运算符。

你可以通过这样做来解决这个问题

using namespace geom_2d::operators;

之前

#include <iostream>

但这在我看来是一个糟糕的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-15
    • 2011-12-03
    • 1970-01-01
    • 2011-11-15
    • 2019-06-18
    • 2020-11-24
    • 2015-04-26
    • 1970-01-01
    相关资源
    最近更新 更多