C++定义内部类的<<运算符

6
在一个我没有启动的项目上工作,我��给一个类添加一个<<运算符。问题是:该类是另一个类的私有内部类,而后者位于一个命名空间中。我无法实现这个目标。
问题可以简化如下:
#include <iostream>
#include <map>
namespace A {
    class B {
        private:
            typedef std::map<int, int> C;
            C a;
            friend std::ostream& operator<<(std::ostream& os, const C &c) {
                for (C::const_iterator p = c.begin(); p != c.end(); ++p)
                    os << (p->first) << "->" << (p->second) << " ";
                return os;
            }
        public:
            B() {
                a[13] = 10;
                std::cout << a << std::endl;
            }
        };
}
int main() {
    A::B c;
}

我尝试使用g++ test.cpp编译它:error: no match for ‘operator<<’。编译器没有找到我的重载函数。我认为在头文件中定义会更简单,但是没有成功。如果您认为在CPP文件中定义更合适,我也可以这样做,但我不知道如何操作。
最后一个要求,我不能使用C++11(很遗憾)。

你的代码使用Visual C++编译器版本15.0(即VS2008,C++11之前)。你正在使用哪个编译器?在ideone.com上无法运行... - Tony Delroy
可能是访问在类中定义的友元函数的重复问题 - 特别是这个答案告诉我们你需要在类外部声明operator<<。然而,这意味着你还需要在类外部定义构造函数。 - BoBTFish
如果我用 class C 替换 typedef ... C,那么我会在类 C 内定义 << 运算符,对吗? - unamourdeswann
2
@user980053:不,这不是内部类,而是std::map的类型别名。因此,ADL仅考虑namespace std,并且在namespace A中找不到您的运算符。 - Mike Seymour
@MikeSeymour:对的!我明白了。所以它实际上是其他帖子的副本。但你知道如何在CPP文件中定义运算符吗? - unamourdeswann
显示剩余5条评论
2个回答

8
由于友元操作符首先在类内声明,因此只能通过参数相关查找使用。然而,它的两个参数类型都不在namespace A中,所以无法找到。 Cstd::map的别名,因此在ADL目的下被视为在namespace std中。
有各种方法可以修复它,但没有一种是完美的:
  • 在类定义之前在namespace A中声明该函数,则它将通过正常查找而不仅仅是ADL可用。然而,这会破坏封装,并可能导致任何其他尝试为std::map重载operator<<的问题。
  • 用命名的静态(非友元)函数替换运算符重载,并按名称调用它。
  • C声明为内部类,而不是std::map的别名。这样可以启用ADL而不破坏封装,但如果您希望它像std::map一样工作,可能有点棘手。

看起来不错!但是我用A::operator<<(std::cout, a);替换了std::cout << a << std::endl;,然后出现了error: ‘operator<<’ is not a member of ‘A’的错误。我做错了吗? - unamourdeswann
@user980053:抱歉,现在我想想那也行不通;操作符只能通过ADL找到。就我个人而言,我可能会放弃重载<<的想法,并编写一个命名函数,例如static void B::print(std::ostream& os, const C &c) - Mike Seymour
@MikeSeymour:既然它已经是完全限定的,为什么还需要被任何东西找到呢? - Lightness Races in Orbit
@LightnessRacesinOrbit:我相当确定,由于它没有在命名空间中声明,即使完全限定,它也只能通过ADL找到。但是我已经接近我的名称查找理解的极限了,如果我再想一下,我的大脑就会熔化。 - Mike Seymour
@MikeSeymour:仔细一想,我记得过去曾因这个荒唐的规则感到震惊和愤怒,所以它确实耳熟能详。我会在标准中花费最多十分钟查找,如果找不到就放弃。 - Lightness Races in Orbit
“friend name injection”是这个问题的关键。然而,在第11.3节中我仍然找不到适用的语言。参考链接:http://en.wikipedia.org/wiki/Barton%E2%80%93Nackman_trick#How_it_works - Lightness Races in Orbit

1

根据Mike Seymour的回答,这里提供第一种解决方案的示例。 需要在B类外定义operator<<()函数,并且暴露出B::C的实际类型。虽不完美但易读...

namespace A {

  // It has to expose the B::C's type
  std::ostream& operator<<(std::ostream& os, const std::map<int, int> &c);

  class B {
  private:
    typedef std::map<int, int> C;
    C a;
    friend std::ostream& operator<<(std::ostream& os, const B::C &c);
  public:
      B() {
        a[13] = 10;
        std::cout << a << std::endl;
      }
    };

  std::ostream& operator<<(std::ostream& os, const B::C &c) {
    for (B::C::const_iterator p = c.begin(); p != c.end(); ++p) {
      os << (p->first) << "->" << (p->second) << " ";
    }
    return os;
  }
}

太棒了!唯一的缺点是我现在需要针对namespace A中每个map<int, int>定义operator<<。我希望我能将定义限制在C中... - unamourdeswann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接