为何std::tuple比较会调用operator bool而非重载的operator<?
问题分析:std::tie比较含隐式operator bool的类型时调用operator bool的原因
问题重现
你编写的代码中,std::tie(c) < std::tie(d)断言失败,原因是编译器将Y类型对象隐式转换为bool后进行比较,而非调用Y的友元operator<。
代码如下:
#include <tuple> struct X { int i = 0; friend constexpr bool operator<(const X &l, const X &r) noexcept { return l.i < r.i; } }; struct Y { int i = 0; constexpr operator bool() const noexcept { return i != 0; } friend constexpr bool operator<(const Y &l, const Y &r) noexcept { return l.i < r.i; } }; int main() { constexpr X a{1}, b{2}; static_assert(std::tie(a) < std::tie(b)); constexpr Y c{1}, d{2}; static_assert(c < d); // 断言失败,因为比较的是true < true,结果为false // static_assert(std::tie(c) < std::tie(d)); return 0; }
原因解析
这个问题的核心在于友元函数的作用域与ADL(依赖于参数的查找)的交互,以及重载决议的优先级:
- 友元函数的可见性:
Y的友元operator<仅在类内部定义,未在全局命名空间中声明。根据C++标准,这类友元函数的名字仅能通过ADL被查找,且只有当调用点的参数类型直接关联到该类时,ADL才会搜索类作用域中的友元函数。 - std::tuple的operator<调用环境:
std::tuple的operator<定义在std命名空间中。当比较std::tuple<Y&>对象时,元素比较的调用点位于std命名空间内。此时编译器会先在std命名空间中查找operator<,未找到后才会触发ADL。 - 重载决议的选择:由于
Y存在隐式转换到bool的运算符,编译器会将“转换为bool后使用内置operator<(bool, bool)”作为候选方案。而在部分编译器实现或标准的历史缺陷中,ADL未能正确找到类内部定义的友元函数,导致编译器选择了隐式转换的路径。
因为c.i=1和d.i=2转换为bool后都是true,true < true的结果为false,最终导致断言失败。
解决方案
要解决这个问题,只需在类外部提前声明友元operator<,使其名字进入全局命名空间,确保ADL或常规查找能找到它:
// 提前声明 struct Y; constexpr bool operator<(const Y &l, const Y &r) noexcept; struct Y { int i = 0; constexpr operator bool() const noexcept { return i != 0; } friend constexpr bool operator<(const Y &l, const Y &r) noexcept { return l.i < r.i; } };
此时std::tie(c) < std::tie(d)会正确调用Y的友元operator<,断言通过。
结论
这并非标准的有意设计,而是C++标准中关于ADL查找类内部友元函数的历史缺陷(已在后续标准修订中修复),部分旧版本编译器可能仍存在此问题。通过在类外部声明友元函数,可以规避该问题。
内容的提问来源于stack exchange,提问作者felix
相关产品推荐
相关产品推荐

