令我惊讶的是,我遇到了另一个障碍,比如C++20 行为用相等运算符破坏现有代码?.
考虑一个简单的不区分大小写的键类型,与例如std::set
or一起使用std::map
:
// Represents case insensitive keys
struct CiKey : std::string {
using std::string::string;
using std::string::operator=;
bool operator<(CiKey const& other) const {
return boost::ilexicographical_compare(*this, other);
}
};
简单测试:
using KeySet = std::set<CiKey>;
using Mapping = std::pair<CiKey, int>; // Same with std::tuple
using Mappings = std::set<Mapping>;
int main()
{
KeySet keys { "one", "two", "ONE", "three" };
Mappings mappings {
{ "one", 1 }, { "two", 2 }, { "ONE", 1 }, { "three", 3 }
};
assert(keys.size() == 3);
assert(mappings.size() == 3);
}
使用 C++17,两个断言都通过(编译器资源管理器)。
切换到 C++20,第二个断言失败(编译器资源管理器)
output.s: ./example.cpp:28: int main(): Assertion `mappings.size() == 3' 失败。
明显的解决方法
一个明显的解决方法是operator<=>
在 C++20 模式下有条件地提供:编译资源管理器
#if defined(__cpp_lib_three_way_comparison)
std::weak_ordering operator<=>(CiKey const& other) const {
if (boost::ilexicographical_compare(*this, other)) {
return std::weak_ordering::less;
} else if (boost::ilexicographical_compare(other, *this)) {
return std::weak_ordering::less;
}
return std::weak_ordering::equivalent;
}
#endif
问题
令我惊讶的是,我遇到了另一种破坏性更改的情况——C++20 在没有诊断的情况下更改了代码的行为。
在我阅读std::tuple::operator<
它时应该有效:
3-6)按字典顺序比较
lhs
,即比较第一个元素,如果它们等价,则比较第二个元素,如果它们等价,则比较第三个元素,依此类推。对于非空元组,(3) 等价于rhs
operator<
if (std::get<0>(lhs) < std::get<0>(rhs)) return true; if (std::get<0>(rhs) < std::get<0>(lhs)) return false; if (std::get<1>(lhs) < std::get<1>(rhs)) return true; if (std::get<1>(rhs) < std::get<1>(lhs)) return false; ... return std::get<N - 1>(lhs) < std::get<N - 1>(rhs);
我知道从技术上讲,这些从 C++20 开始就不再适用,它被替换为:
通过综合三向比较(见下文)按字典顺序进行比较
lhs
,rhs
即比较第一个元素,如果它们相等,比较第二个元素,如果它们相等,比较第三个元素,依此类推
和...一起
<、<=、>、>= 和 != 运算符分别由
operator<=>
和合成operator==
。(C++20 起)
事情是,
我的类型没有定义
operator<=>
也不operator==
,并且正如这个答案所指出的那样,提供
operator<
额外的东西会很好,并且应该在评估简单的表达式时使用,比如a < b
.
- C++20 中的行为更改是否正确/故意?
- 应该有诊断吗?
- 我们可以使用其他工具来发现像这样的无声破损吗?感觉就像扫描整个代码库以在
tuple
/中使用用户定义的类型pair
并不能很好地扩展。 tuple
除了/之外,还有其他类型pair
可以表现出类似的变化吗?