为何C++20中std::pair比较优先选择类型转换而非自定义operator<?
C++20中std::pair比较行为变化的原因分析
切换到C20后,部分测试用例出现失败,以下代码在C17和C++20模式下输出结果存在明显差异:
class MyString { public: operator char const *() const { std::printf("convert "); return nullptr; } bool operator<(MyString const & other) const { std::printf("MyString::operator< "); return 1; } }; int main() { MyString s1; MyString s2; std::printf("s1 < s2 = %d\n", s1 < s2); std::pair<MyString, MyString> pair1; std::pair<MyString, MyString> pair2; std::printf("pair1 < pair2 = %d\n", pair1 < pair2); }
C++17输出
MyString::operator< s1 < s2 = 1 MyString::operator< pair1 < pair2 = 1
C++20输出
MyString::operator< s1 < s2 = 1 convert convert convert convert pair1 < pair2 = 0
看起来std::pair::operator<=>会优先选择通过隐式转换合成的比较逻辑,而非MyString自定义的operator<,这是为什么?
注:已知将转换运算符设为explicit可以解决该问题,但希望了解导致此行为的根本原因。
核心原因解析
1. C17与C20中std::pair比较机制的本质区别
- C++17及更早版本:
std::pair::operator<是显式实现的字典序比较,逻辑为逐元素调用operator<:先比较first成员,若lhs.first < rhs.first为真则返回true;若rhs.first < lhs.first为真则返回false;再对second成员执行相同逻辑。因此对于MyString类型,会直接调用你自定义的operator<,不会触发隐式转换。 - C++20版本:
std::pair默认生成三路比较运算符operator<=>,其逻辑为逐元素执行三路比较:先对first成员执行lhs.first <=> rhs.first,若结果不为equal则直接返回;否则再对second成员执行同样操作。同时,C++20中operator<会由operator<=>自动合成(若用户未自定义)。
2. MyString的重载决议路径选择
你的MyString仅定义了二元布尔型operator<,并未定义operator<=>。根据C++20规则,当用户自定义了operator<时,编译器不会自动为类合成operator<=>。
当std::pair的operator<=>尝试比较MyString成员时,编译器找不到适配的operator<=>,于是会寻找其他可行的比较路径:
MyString提供了**隐式转换到const char***的运算符,而const char*作为指针类型,在C++20中拥有默认的operator<=>(属于基本类型的三路比较实现)。- 这条转换路径是合法且可行的,因此编译器会将两个
MyString对象分别转换为const char*(每次转换触发convert输出),再执行三路比较——这就是四次convert输出的由来(pair的first和second成员各转换一次)。
3. 最终结果差异的来源
转换后的const char*均为nullptr,三路比较结果为equal,因此std::pair的整体比较结果是相等的。而C20中由operator<=>合成的operator<在比较结果为相等时会返回false,所以输出0;而C17中直接调用MyString::operator<返回true,因此输出1。
内容的提问来源于stack exchange,提问作者Osyotr
相关产品推荐
相关产品推荐

