STL 中使用 operator== 的命名空间解析

Namespace resolution with operator== in the STL

考虑一个简单的类型,在一个命名空间中,带有 operator==:

namespace ANamespace {
    struct Foo { int i; float f; };
}

#ifdef INSIDE

namespace ANamespace {
    bool operator==(const Foo& l, const Foo& r)
    {
        return l.i == r.i && l.f == r.f;
    }
}

#else

bool operator==(const ANamespace::Foo& l, const ANamespace::Foo& r)
{
    return l.i == r.i && l.f == r.f;
}

#endif

bool compareElements(const std::vector<ANamespace::Foo>& l, const std::vector<ANamespace::Foo>& r)
{
    return l == r;
}

如果在 ANamespace 中定义了 operator==(通过定义 INSIDE),该示例将编译。但是,如果 operator== 是在全局命名空间中定义的(#else 情况),则函数 compareElements() 不会编译 - 无论是在 GCC 和 Clang 中,还是在 libstdc++ 和 libc++ 中。所有这些都发出模板错误:

In file included from /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../include/c++/9.2.0/vector:60:
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/stl_algobase.h:820:22: error: invalid operands to binary expression ('const ANamespace::Foo' and 'const ANamespace::Foo')
            if (!(*__first1 == *__first2))
                  ~~~~~~~~~ ^  ~~~~~~~~~
...

但是,在函数中直接比较两个Foo,例如

bool compareDirectly(const ANamespace::Foo& l, const ANamespace::Foo& r)
{
    return l == r;
}

似乎无论在哪里定义 operator== 都可以正常工作。

标准中是否有关于 STL 期望 operator== 定义的位置的规则?

!(*__first1 == *__first2) 发生在函数模板 std::operator== 中,因此它被认为是依赖的非限定函数调用表达式,因此在重载解析期间仅在 [=11= 的定义上下文中找到函数] 和那些通过 ADL 找到的是候选人。

很明显,在标准比较运算符的定义上下文中没有声明 operator==(const Foo&, const Foo&)。在参数相关查找 (ADL) 中,检查每个参数的名称空间以搜索调用的可行函数,因此这就是为什么在 ANamespace 中定义 operator== 有效。

简而言之,在声明 class 的同一个命名空间中声明 operator== 可以保证依赖于参数的查找会找到它,所以这就是您应该做的。该标准并未强制要求您遵循此约定,但在实践中这是获得保证的唯一途径。这也适用于标准库可能对您的类型调用的其他运算符。

如果您选择在全局命名空间中声明 operator== 但您的类型是 而不是 在全局命名空间中声明,标准库算法有可能会仍然可以通过不合格的名称查找找到您的 operator==。但是,不能保证这有效,因为不合格的名称查找将 停止 在找到 operator== 的最内层封闭命名空间。换句话说,在

形式的算法中
namespace std {
template< class InputIt1, class InputIt2 >
constexpr bool equal( InputIt1 first1, InputIt1 last1,
                      InputIt2 first2 ) {
    // ...
}
}

operator== 的非限定名称查找将找到在 std 命名空间中声明的任何 operator==(当然,这不适用于您的用户定义类型)和然后,如果它在 std 中找到任何内容,即使它可能不是可行的重载,也不会在全局命名空间中查找。

您需要阅读 "ADL" 又名 "Argument Dependent Lookup"。

基本上,当您编写 v1 == v2 时,编译器会在当前命名空间中寻找带有两个正确类型 ANamespace::Foo 参数的 operator==(注意:我们在这里忽略转换)。如果找不到,那么它将在 (ANamespace).

中定义类型的命名空间中查找

Wikipedia 有一篇关于此的文章。