理解运算符查找;哪个编译器是正确的?

时间:2014-11-04 16:24:06

标签: c++ templates namespaces operator-overloading

之前可能会问过,但我没有找到答案的运气......

我有一个无序的容器(即一个哈希;我们称之为QHash,因为它可能会发生在任何类似的情况下),它需要一个比较运算符才能获得它的密钥类型。 / p>

请考虑以下事项:

// foo.h
class Bar
{
public:
    class Foo {};
};

// foo.cpp
#include <QtCore/QHash>

namespace
{
    typedef Bar::Foo Foo;
    bool operator==(Foo const& a, Foo const& b) { return &a == &b; }
}

uint qHash(Foo const& foo) { return qHash(&foo); }

int main()
{
    QHash<Foo, int> hash;
    // do stuff with hash, e.g.:
    hash.insert(Foo(), 5);
    return 0;
}

使用G ++,一切都很好。但是,clang在qhash.h的内容中给出了一个错误invalid operands to binary expression,其中==试图在Foo的实例上使用operator==。在我看来,clang要么没有找到或拒绝匿名命名空间中{{1}}的定义,可能是因为查找规则与G ++不同。

我想知道,哪个编译器是正确的?

P.S。我在C ++ 11模式下构建,以防它有所作为。

3 个答案:

答案 0 :(得分:5)

好吧,ADL会查看定义类的命名空间。看看 在 [basic.lookup.argdep] / 2: &#34;对于函数调用中的每个参数类型T,都有一组零 或更多关联的命名空间和a 要考虑的零个或多个关联类的集合。套 命名空间和类已确定 完全由函数参数的类型(和。的名称空间) 任何模板模板参数)。 Typedef名称和用于指定类型的using声明不会 为这一集做出贡献。&#34;

请注意最后一句。

因此,对于原始示例,命名空间中的typedef 运营商没有帮助, gcc错了,clang是对的。

答案 1 :(得分:5)

这是known GCC bug。本质上,问题是GCC没有正确实现运营商名称的两阶段名称查找。非限定查找找不到operator==(因为声明太晚了),并且找不到依赖于参数的名称查找(因为::(anonymous namespace)不是Bar::Foo的关联命名空间,所以不应在qHash的实例化中考虑。

如果将qHash的定义移到匿名命名空间中,GCC将拒绝该代码,因为它的错误仅适用于操作员名称而不适用于正常的函数名称。

答案 2 :(得分:-1)

尽管您声称选择无序容器并不重要,但以下自包含的完全标准代码会在g ++和clang上生成一致的错误 - 两者都找不到operator==

#include <unordered_map>

// foo.h
class Bar
{
public:
    class Foo {};
};

// foo.cpp
namespace
{
    typedef Bar::Foo Foo;
    bool operator==(Foo const& a, Foo const& b) { return &a == &b; }
}

namespace std
{
  template <>
  struct hash<Foo>
  {
    std::size_t operator()(const Foo& k) const
    {
      return std::hash<const Foo*>()(&k);
    }
  };
}

int main()
{
    std::unordered_map<Foo, int> hash;
    // do stuff with hash, e.g.:
    hash.emplace(Foo(), 5);
    return 0;
}

错误的重要部分是

  

错误:operator==不匹配(操作数类型为const Bar::Fooconst Bar::Foo

  { return __x == __y; }

基本上,如果您希望通过库代码找到运算符,请将它们放在类的关联命名空间中,让ADL执行它的操作。绝对不要使用匿名命名空间,除非该类本身位于匿名命名空间内。