标准库模板中运算符的非限定查找
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 朋友,这些朋友无法通过不合格的查找找到。无论如何依赖它显然是不明智的。
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 朋友,这些朋友无法通过不合格的查找找到。无论如何依赖它显然是不明智的。