运算符如何在名称空间中重载解析?

时间:2012-07-06 09:31:49

标签: c++ operators overload-resolution

我发现C ++解析运算符重载的奇怪行为,我无法解释自己。指向描述它的某个资源的指针就像答案一样好。

我有2个翻译单元。在一个(称为util.cpp / h)中,我声明并定义了两个运算符(我省略了readabilty的实际实现,无论如何都会发生problam):

// util.h
#ifndef GUARD_UTIL
#define GUARD_UTIL

#include <iostream>

std::istream& operator>>(std::istream& is, const char* str);
std::istream& operator>>(std::istream& is, char* str);
#endif

//util.cpp
#include "util.h"
#include <iostream>

std::istream& operator>>(std::istream& is, const char* str) {
  return is;  
}
std::istream& operator>>(std::istream& is, char* str) {
  return is;  
}

如果这些运算符在全局命名空间中,那么它们是在std类型和内置类型上运行的,并且应该可以从任何地方使用。它们只能从全局命名空间(例如从main())或明确地告诉编译器它们在全局命名空间中工作(参见代码示例)。

在另一个翻译单元(称为test.cpp / h)中,我在命名空间中使用这些运算符。这有效,直到我将类似的运算符放入此命名空间。一旦添加此运算符,编译器(例如gcc或clang)就无法找到可行的运算符&gt;&gt;了。

// test.h
#ifndef GUARD_TEST
#define GUARD_TEST

#include <iostream>

namespace Namespace {
  class SomeClass {   
    public:
      void test(std::istream& is);
  };

  // without the following line everything compiles just fine
  std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
}

#endif

//test.cpp
#include "test.h"
#include "util.h"
#include <iostream>

void Namespace::SomeClass::test(std::istream& is) {
  ::operator>>(is, "c"); //works
  is >> "c" //fails
}

为什么编译器在没有运算符时会找到正确的运算符&gt;&gt;在命名空间中,但没有找到什么时候?为什么运算符会影响编译器找到正确的编译器的能力,即使它具有不同的签名?

解决这个问题的一个尝试是放

的std :: istream的&安培;运算符&gt;&gt;(std :: istream&amp; is,const char * str){:: operator&gt;&gt;(is,str); }

进入Namespace,但是链接器抱怨以前的定义。附加:为什么链接器会找到编译器找不到的东西?

3 个答案:

答案 0 :(得分:9)

这是隐藏名称的问题。标准说(c ++ 03,3.3.7 / 1)

  

可以通过在嵌套声明性区域中显式声明相同名称或派生名称来隐藏名称   class(10.2)。

您案例中的“名称”将为operator>>,名称空间构成嵌套的声明性区域。

最简单的方法是使用using声明来声明命名空间 - 本地operator<<

namespace your_namespece {
    std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
    using ::operator>>;
}

请注意,此功能不会干扰Koenig查找(至少在您的情况下,原则上它可以),因此仍会找到std::的IO运算符。

PS:解决此问题的另一种可能性是将SomeClass的运算符定义为inline friend。这些函数在命名空间级别(在“他们的”类之外)声明,但从那里看不到。它们只能通过Koenig查找找到。

答案 1 :(得分:3)

这里有几个问题;首先,你要重新定义一个 std::中已存在的全局命名空间中的函数。该 但是,您描述的问题是由于名称查找的工作方式。 基本上,在运算符重载的情况下,编译器会执行两次 名称查找。第一个(用于所有符号,不仅仅是运算符) 从符号出现的范围开始,向外工作:首先 本地块,然后是类,它的基类(如果有的话)和 最后是命名空间,在全局命名空间中工作。一个 这种查找的一个重要特征是它会停止 范围它找到名称:如果它在本地范围内找到一个名称,它不会 看看任何课程;如果它在一个类中找到一个,它就不会查找 基类或命名空间,如果它在命名空间中找到它,它 不查看任何封闭的命名空间。就这个查找而言 有关,所有重载必须在同一范围内。第二次查找 仅影响函数和运算符重载,并在上下文中发生 用作参数的类或对象;因此,如果其中一个操作数是 标准库中的一个类(或者从类中派生的任何类) 标准库),编译器将查找std::中的函数,甚至 虽然使用符号的上下文不包括std::。 您遇到的问题是内置类型(如char*)没有 暗示任何命名空间(甚至不是全局):给出你的重载,第一个 查找将在它看到的第一个operator>>处停止,第二个将停止 查看std::。你的功能都不是。如果你想要一个 要找到重载的运算符,必须在范围内定义它 其中一个操作数。

具体来说,这里:你不能重载std::istream& operator>>( std::istream&, char* ),因为它已经过载了 标准库。 std::istream& operator>>( std::istream&, char const* )是可能的,但我不确定它应该做什么,因为 它无法写入第二个操作数。更一般地说,你应该只 为您定义的类型重载此运算符,您应该这样做 将重载放在与类型本身相同的命名空间中,以便它 将在上面的第二个查找中找到(称为Argument Dependent 查找,或ADL-或更早,Koenig查找,之后的人 发明了它。)

答案 2 :(得分:0)

::是全局范围,因此,编译器必须扫描全局命名空间并找到此运算符。 是&gt;&gt; “C”,试图找到运算符&gt;&gt;在命名空间中,编译器找到它并停止搜索,然后编译器尝试选择具有所需签名的运算符,如果没有这样的运算符 - 编译器失败。 我想你应该阅读Herb Sutter Exceptional C ++。