Каким образом оператор трехстороннего сравнения отличается от вычитания?
В С++ 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));
}
Они имеют тот же эффект. Я не могу понять разницу.
Ответы
Ответ 1
Оператор решает проблему с числовым переполнением, которое вы получаете с вычитанием: если вы вычитаете большое положительное число из отрицательного значения, близкого к INT_MIN
, вы получаете число, которое не может быть представлено как int
, таким образом вызывая поведение undefined.
Несмотря на то, что версия 3 не содержит этой проблемы, она совершенно не удобочитаема: для того, кто никогда раньше не видел этого трюка, потребуется некоторое время. Оператор <=>
также исправляет проблему читаемости.
Это только одна проблема, которую требует новый оператор. Раздел 2.2.3 Herb Sutter Согласованный сравнительный документ рассказывает об использовании <=>
с другими типами данных языка, где вычитание может приводить к несогласованным результатам.
Ответ 2
Вот некоторые случаи, в которых вычитание не будет работать:
-
unsigned
.
- Операнды, вызывающие переполнение целых чисел.
- Определяемые пользователем типы, которые не определяют
operator -
(возможно, потому что это не имеет смысла - можно определить порядок без определения понятия расстояния).
Я подозреваю, что этот список не является исчерпывающим.
Конечно, можно придумать обходные пути, по крайней мере, для # 1 и # 2. Но целью operator <=>
является инкапсуляция этого уродства.
Ответ 3
Здесь есть несколько значимых ответов, но Херб Саттер в в своей статье говорит:
< = > для разработчиков типов: код пользователя (включая общий код) за пределами реализации оператора <= > должен почти никогда не ссылаться непосредственно на <= > (как уже было обнаружено как хорошая практика на других языках);
Таким образом, даже если не было никакой разницы, точка оператора различна: чтобы авторы классов могли создавать операторы сравнения.
Основное различие между оператором вычитания и оператором "космического корабля" (согласно предложению Саттера) заключается в том, что перегрузка operator-
дает вам оператор вычитания, тогда как перегрузка operator<=>
:
- дает вам 6 основных операторов сравнения (даже если вы объявляете оператора как
default
: никакого кода для записи!);
- объявляет, сопоставим ли ваш класс, сортируется и является ли порядок полным или частичным (сильный/слабый в предложении Sutter);
- позволяет проводить гетерогенные сравнения: вы можете перегрузить его, чтобы сравнить свой класс с любым другим типом.
Другие значения относятся к возвращаемому значению: operator<=>
возвращает enum
класса, класс указывает, является ли тип сортируемым и является ли тип сильным или слабым. Возвращаемое значение преобразуется в -1, 0 или 1 (хотя Саттер оставляет место для типа возврата, а также указывает расстояние, как это делает strcmp
). В любом случае, если принять возвращаемое значение -1, 0, 1, мы наконец получим истинную функцию signum в С++! (signum(x) == x<=>0
)