三路比较运算符和减法有何不同?

62

C++20中引入了新的比较运算符<=>,但我认为在大多数情况下,简单的减法运算就可以胜任:

有一个新的比较运算符<=>,但是我认为在大多数情况下使用简单的减法即可。

int my_strcmp(const char *a, const char *b) {
    while (*a == *b && *a != 0 && *b != 0) {
        a++, b++;
    }
    // Version 1
    return *a - *b;
    // Version 2
    return *a <=> *b;
    // Version 3
    return ((*a > *b) - (*a < *b));
}

它们有相同的效果。我真的不太理解它们之间的区别。


25
整数减法是一种旧的技巧,用于进行三路比较,但它可能会受到溢出的影响。对于无符号类型,它也不总是有效。替代方法是 ((*a > *b) - (*a < *b))。 - miravalls
甚至有人提出允许任何具有默认 <=> 的类型成为非类型模板参数的讨论。这个运算符的影响超出了仅仅替换一种仅适用于算术类型的操作。 - chris
1
@iBug:那么...你计划如何对不是字符数组的东西进行三向比较? - Nicol Bolas
@edc65 这就是我所问的。 - iBug
1
@wvxvw 你是不是想说 (2 ** (sizeof(char) * CHAR_BIT)) - iBug
显示剩余4条评论
3个回答

59

这个运算符解决了减法产生数值溢出的问题:如果你从一个接近INT_MIN的负数中减去一个大正数,就会得到一个不能表示为int的数字,从而导致未定义的行为。

虽然版本3已经没有了这个问题,但它缺乏可读性:对于从未见过这种技巧的人来说,需要一些时间才能理解。 <=> 运算符也解决了可读性问题。

这是新运算符解决的问题之一。Herb Sutter的Consistent comparison论文第2.2.3节讨论了在语言的其他数据类型中使用<=>运算符的情况,在那里减法可能会产生不一致的结果。


1
请问您能否解释一下理解第三个版本的技巧吗?在我看来,它似乎是 (false/true - true/false)。 - asgs
8
这个技巧利用了 C/C++ 中布尔值的“对偶性”,其中由比较运算符返回的 truefalse 值实际上分别是整数 10这个 Q&A提供了更多关于此技巧的详细信息。 - Sergey Kalinichenko

43

这里有一些减法无法使用的情况:

  1. unsigned 类型。
  2. 导致整数溢出的操作数。
  3. 用户定义类型未定义 operator -(也许是因为它没有意义 - 一个人可以定义一个顺序而不定义距离概念)。

我怀疑这个列表并非详尽无遗。

当然,至少可以针对#1和#2提出解决办法。但 operator <=> 的目的是封装这种丑陋的细节。


17
请注意,在字符串上进行3路比较(不仅是const char*,而是实际的字符串类)是一个合理的操作。但是,对两个字符串进行减法运算是不合理的。 - Nicol Bolas

18
这里有一些有意义的答案,关于它们之间的差异,但是Herb Sutter在他的论文中明确表示:

<=>是为类型实现者设计的:使用者代码(包括通用代码)在不涉及<=>实现的情况下,几乎永远不应直接调用<=>(正如其他语言已经发现的良好实践);

因此,即使没有区别,运算符的重点也不同:帮助类编写者生成比较运算符。
根据Sutter的提议,减法运算符和“飞船”运算符之间的核心区别在于:重载operator-会给你一个减法运算符,而重载operator<=>会:
  • 给您6个核心比较运算符(即使您将运算符声明为default:无需编写任何代码!);
  • 声明您的类是否可以比较、可排序以及顺序是总体还是部分(在Sutter的提议中是强/弱);
  • 允许进行异构比较:您可以将其重载以将您的类与任何其他类型进行比较。
其他区别在于返回值: operator<=>会返回一个类的enum,该类指定类型是否可排序以及排序是强还是弱。返回值将转换为-1、0或1(尽管Sutter留有余地让返回类型也能表示距离,如strcmp所做的那样)。无论如何,假设返回值为-1、0、1,则我们最终将得到C ++中真正的符号函数!(signum(x) == x<=>0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接