【问题标题】:understanding operator lookup; which compiler is correct?理解运算符查找;哪个编译器是正确的?
【发布时间】:2014-12-31 15:12:50
【问题描述】:

这可能以前被问过,但我没有找到答案......

我有一个无序容器(即哈希;我们将其称为 QHash,因为它是,尽管这可能发生在任何类似的情况下)需要一个比较运算符来作为它的键类型。

考虑以下几点:

// 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 的实例上使用 ==。在我看来,clang 不是在匿名命名空间中找到或拒绝 operator== 的定义,可能是由于与 G++ 不同的查找规则。

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

附言我在 C++11 模式下构建,以防它有所作为。

【问题讨论】:

  • 内容很重要。我们需要一个实际的可编译示例和错误消息。
  • 让 SO 读者猜测是什么密码产生了模糊描述的行为的好主意。我几乎为此投了赞成票。不过,我看到至少有 4 人已经这样做了,所以我弃权。
  • @jrok,不,他们真的没有(事实上,空的会很好)。问题是:“鉴于上述情况,是否期望 QHash 尝试在一对 Foo 上使用 operator== 会找到定义的运算符?”。 GCC 认为“是”。 Clang 认为“不”。我询问如何解决它(“不要那样做”/使 operator== public);我想知道哪个是正确的。
  • ...在有人告诉我之前我需要解释无序容器的概念... QHash (显然)是&lt;Key, Value&gt; 上的一个模板类,带有一个关联的帮助类QHashNode&lt;Key, Value&gt;成员Key key 和方法(Key const&amp; key0) 执行key0 == key。 Clang barfs here,抱怨找不到合适的==
  • 嗯,std::unordered_map 有同样的问题吗?

标签: c++ templates namespaces operator-overloading


【解决方案1】:

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

注意最后一句话。

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

【讨论】:

  • 谢谢;这是正确的!
  • 除了操作符使用的命名空间也会影响查找,OP并没有告诉我们调用代码在哪个命名空间。
【解决方案2】:

这是known GCC bug。本质上,问题在于 GCC 没有正确实现运算符名称的两阶段名称查找。您的operator== 未通过非限定查找找到(因为它被声明得太晚)并且未通过参数相关名称查找找到(因为::(anonymous namespace) 不是Bar::Foo 的关联命名空间,因此不应考虑在qHash 的实例化。

如果将qHash 的定义移动到匿名命名空间中,GCC 将拒绝该代码,因为它的错误仅适用于运算符名称,而不适用于普通函数名称。

【讨论】:

    【解决方案3】:

    尽管您声称无序容器的选择无关紧要,但以下自包含的完全标准代码在 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 完成它的工作。绝对不要使用匿名命名空间,除非类本身在匿名命名空间内。

    【讨论】:

    • 我说key class的内容无关紧要。他们没有。我相信容器类“可能”并不重要,但显然它确实如此。
    • (对不起;所以将输入 cmets 视为“是的,我想发布那个”......没有完成......)但是,虽然我认为你暗示了一个答案,但我'我想问的是,GCC 是否也应该拒绝原始代码(就像 clang 一样)?
    • @Matthew:如果没有容器的完整源代码,就无法知道。如果您希望 所有 容器找到 operator==,请将其放在关联的命名空间中。
    • 它不是“我的”容器...我猜你从未听说过 Qt?见qt.gitorious.org/qt/qtbase/source/5.3:src/corelib/tools/qhash.h
    • @Matthew:我听说过,但你决定使用它。掌握您的决定。当您开始依赖内部实现细节时(而且您正在编写的代码在标准 std::unordered_map 被替换时不起作用),那么您需要将这些实现细节视为 您的 i> 代码。否则,您可能会冒着未来的 Qt 更改破坏整个应用程序的风险。
    猜你喜欢
    • 2016-09-15
    • 1970-01-01
    • 2019-08-06
    • 2014-11-02
    • 2021-11-30
    • 1970-01-01
    • 2021-07-31
    • 2011-07-24
    • 2011-07-31
    相关资源
    最近更新 更多