C++is_eq is_neq is_lt等比较谓词
C++20里那些“不声不响”的比较谓词:is_eq、is_neq、is_lt……到底该不该用?
你有没有在翻阅 <compare> 头文件时,偶然瞥见 std::is_eq、std::is_neq、std::is_lt 这些名字,心里一愣:“这不就是 ==、!=、< 吗?C++ 还要专门给它们起个新名字?”
别急——它们真不是语法糖,也不是为了凑数。它们是为三路比较(operator<=>)落地而生的语义锚点,专治“比较结果被误读”这个老毛病。
事情得从 C++20 的三路比较说起。以前写比较逻辑,我们常这样干:
if (a < b) { /* ... */ }
else if (a == b) { /* ... */ }
else { /* ... */ }
看似自然,但问题藏在细节里:如果 a 和 b 是自定义类型,且只定义了 operator<=>(比如 strong_ordering),那 a == b 其实是调用 std::is_eq(a <=> b),而不是直接调用 operator==。编译器不会自动把 == 翻译成三路比较的结果判断——除非你显式用对工具。
这就是 is_eq 等谓词的真实定位:它们不是替代运算符,而是解包三路比较结果的“安全扳手”。
比如:
auto cmp = a <=> b;
if (std::is_lt(cmp)) { /* a < b */ }
else if (std::is_eq(cmp)) { /* a == b */ }
else if (std::is_gt(cmp)) { /* a > b */ }
// 注意:这里没有 else —— 因为 cmp 不可能是“未定义”状态(只要类型支持三路比较)
这段代码比连用 == 和 < 更可靠。为什么?因为 std::is_eq(cmp) 明确检查 cmp == 0,而 a == b 在某些上下文中可能退回到用户自定义的 operator==——哪怕你本意只想基于 <= 的语义做分支。
更实际的场景在泛型代码里。假设你写一个通用的二分查找辅助函数:
template<typename T, typename U>
bool binary_search_hint(const std::vector<T>& v, const U& key) {
auto it = std::lower_bound(v.begin(), v.end(), key);
if (it != v.end() && std::is_eq(*it <=> key)) {
return true;
}
return false;
}
这里用 std::is_eq(*it <=> key) 而非 *it == key,是因为:
- 如果
T和U类型不同(比如T=int,U=long),*it == key可能触发隐式转换+自定义operator==,行为不可控; - 而
*it <=> key会尝试合成三路比较(C++20 支持混合类型合成),std::is_eq则只关心其返回值是否为零——语义干净、无歧义、不依赖额外重载。
再看一个容易踩坑的例子:std::is_neq 并不等价于 !std::is_eq。
乍看荒谬,但请看:
auto cmp = 3.0 <=> NaN; // 结果是 std::partial_ordering::unordered
std::cout << std::is_eq(cmp) << "\n"; // false
std::cout << std::is_neq(cmp) << "\n"; // false —— 注意!不是 true
std::cout << !std::is_eq(cmp) << "\n"; // true
NaN <=> NaN 是 unordered,既不是 equal,也不是 less 或 greater,所以 is_eq 和 is_neq 都返回 false。is_neq 的语义是“明确小于或大于”,而非“不等于”。 它对应的是 cmp < 0 || cmp > 0,不是 !(cmp == 0)。
这个细节在处理浮点、数据库 NULL 语义、或自定义 partial ordering 类型时,直接决定逻辑是否健壮。
那么,什么时候该用它们?三个明确信号:
✅ 你在处理 operator<=> 返回值,且需要分支判断;
✅ 你在写泛型算法,想绕过用户可能重载的 ==/!= 带来的不确定性;
✅ 你在和 std::partial_ordering 打交道,需要区分 “uncomparable” 和 “not equal”。
什么时候不该用?
❌ 单纯判断两个 int 是否相等——a == b 更直白,也更快;
❌ 在 constexpr 上下文中盲目套用(虽然它们都是 constexpr,但无谓增加间接层);
❌ 把 is_neq 当作 != 的替代品来写条件——它不覆盖 unordered 场景,容易漏逻辑。
顺带提一句命名风格:is_eq 不叫 is_equal,is_lt 不叫 is_less_than,是刻意为之。C++ 标准委员会希望这些名字短、易扫读、与 std::less/std::equal_to 等谓词家族保持视觉节奏一致。它们不是教学文档,而是工具箱里的快拧螺丝刀——轻、准、不占地方。
最后一点体感:这些谓词真正价值,不在“多了一个写法”,而在把比较意图从隐式推导变成显式声明。就像你不会在严肃代码里写 if (x - y) 来代替 if (x != y),同理,当你看到 std::is_eq(a <=> b),你就知道——作者明确选择了三路比较语义,且只关心相等性这一维度。这种可读性,在协作和维护中省下的沟通成本,远超几行代码的长度。
所以,下次看到 is_eq,别把它当语法彩蛋。它是 C++20 比较体系里一根沉默但关键的承重梁——不抢眼,但少它一寸,整栋泛型逻辑楼就可能歪一点。


还没有评论,来说两句吧...