运算符重载解析如何在名称空间内工作?

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

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

// 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)就无法再找到可行的操作符>>。

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

为什么编译器在命名空间中没有运算符>>时找到正确的运算符,但在找到运算符时却无法找到它? 为什么操作符会影响编译器找到正确的,即使它具有不同的签名的能力?

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

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

进入命名空间,但是链接器会抱怨以前的定义。 所以额外的:为什么连接器会发现编译器找不到的东西?


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

一个名称可以通过在嵌套声明区域或派生类中显式声明同一名称来隐藏(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的操作员定义为内联friend 。 这些函数在名称空间级别(在“他们的”类之外)声明,但从那里不可见。 他们只能通过Koenig查找找到。


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

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


::是全局作用域,因此编译器必须扫描全局名称空间并找到此运算符。 是>>“C”,试图在Namespace中查找operator >>,所以,编译器找到它并停止搜索,然后编译器尝试选择具有所需签名的运算符,如果没有这样的运算符 - 编译器失败。 我认为你应该阅读Herb Sutter Exceptional C ++。

链接地址: http://www.djcxy.com/p/73065.html

上一篇: How does the operator overload resolution work within namespaces?

下一篇: Where should I define operator >> for my specialization of std::pair?