标准库模板中运算符的非限定查找

Unqualified lookup of operators in standard library templates

namespace N {
    struct A {};
    
    template<typename T>
    constexpr bool operator<(const T&, const T&) { return true; }
}

constexpr bool operator<(const N::A&, const N::A&) { return false; }

#include<functional>

int main() {
    static_assert(std::less<N::A>{}({}, {}), "assertion failed");
}

https://godbolt.org/z/vsd3qfch6

这个程序在看似随机版本的编译器上编译。

断言在 v19.15 之后的所有版本的 MSVC 上都失败,但在 v19.14 上成功。

它在 GCC 11.2 及之前的版本上成功,但在当前的 GCC trunk 上失败。

它在所有版本的 libstdc++ Clang 上都失败了。它在所有版本的 libc++ 中都成功,包括当前主干,版本 13 除外。

使用 ICC 总是成功。


是否指定static_assert是否应该成功?


这里的根本问题是 std::less 在内部使用 <,因为它在模板中使用,所以会从实例化点通过依赖于参数的查找找到 operator< 重载(这是正确的方法),但也可以从模板定义的角度通过非限定名称查找。

如果找到全局重载,则匹配更好。不幸的是,这使得程序行为依赖于标准库包含的位置和顺序。

我原以为标准库会禁用 std 命名空间外的非限定名称查找,因为无论如何都不能依赖它,但这应该得到保证吗?

这里重要的是 std 内部的非限定查找是否在到达全局命名空间之前找到任何 other operator<(无论其签名如何!) .这取决于包含了哪些 headers(任何标准库头文件都可能包含任何其他头文件),并且还取决于自 C++20 replaced 许多这样的运算符带有 operator<=>。此外,有时此类事物会被重新指定为 hidden 朋友,这些朋友无法通过不合格的查找找到。无论如何依赖它显然是不明智的。