Чем оператор трехстороннего сравнения отличается от вычитания?

В 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));
}

У них такой же эффект. Я не могу понять разницу.


person iBug    schedule 31.12.2017    source источник
comment
Целочисленное вычитание - это старый прием для выполнения трехстороннего сравнения, но он может страдать от переполнения. Это не работает всегда и для беззнаковых типов. Альтернативный способ - ((* a ›* b) - (* a‹ * b))   -  person miravalls    schedule 31.12.2017
comment
Был даже разговор о том, чтобы разрешить любому типу со значением по умолчанию <=> быть параметром шаблона, не являющимся типом. У этого оператора есть последствия, выходящие за рамки замены одной операции, которая работает только с арифметическими типами.   -  person chris    schedule 31.12.2017
comment
@iBug: Итак ... что именно вы планируете делать для трехстороннего сравнения вещей, которые не являются массивами символов?   -  person Nicol Bolas    schedule 31.12.2017
comment
Как вы сказали, в большинстве случаев хорошо работает простое вычитание. А как насчет всех остальных случаев?   -  person edc65    schedule 31.12.2017
comment
@ edc65 Вот о чем я спрашиваю   -  person iBug    schedule 01.01.2018
comment
Вам повезло в том смысле, что вы работаете с небольшими доменами. Вы можете просто написать тест, который исчерпывающе проверяет, работает ли ваша функция так, как вы ожидаете (вам нужно сделать только (2 ** sizeof(char)) * (2 ** sizeof(char)) сравнения).   -  person wvxvw    schedule 01.01.2018
comment
@wvxvw Вы имели в виду (2 ** (sizeof(char) * CHAR_BIT))?   -  person iBug    schedule 01.01.2018
comment
@iBug да, это.   -  person wvxvw    schedule 01.01.2018
comment
Это может показаться несущественным, но: Если *a == *b && *a != 0, то мы уже знаем это *b != 0, поэтому мы не должны включать эту проверку в цикл.   -  person Elliott    schedule 10.03.2020


Ответы (3)


Оператор решает проблему с числовым переполнением, возникающую при вычитании: если вы вычтите большое положительное число из отрицательного, близкого к INT_MIN, вы получите число, которое не может быть представлено как int, что приведет к неопределенному поведению.

Хотя в версии 3 эта проблема отсутствует, ей совершенно не хватает удобочитаемости: потребуется некоторое время, чтобы понять, кто-то, кто никогда раньше не видел этого трюка. Оператор <=> также устраняет проблему с удобочитаемостью.

Это только одна проблема, которую решает новый оператор. Раздел 2.2.3 Последовательное сравнение Херба Саттера < В / em> paper говорится об использовании <=> с другими типами данных языка, где вычитание может привести к противоречивым результатам.

person Sergey Kalinichenko    schedule 31.12.2017
comment
не могли бы вы объяснить, как понять 3-ю версию? Мне кажется, что (ложь / правда - правда / ложь) - person asgs; 02.01.2018
comment
@asgs Этот трюк использует двойственность логических значений в C / C ++, где значения true и false, возвращаемые операторами сравнения, на самом деле являются целыми числами 1 и 0 соответственно. В этом разделе вопросов и ответов содержится более подробная информация об этом трюке. - person Sergey Kalinichenko; 02.01.2018

Вот некоторые случаи, в которых вычитание не работает:

  1. unsigned типы.
  2. Операнды, вызывающие целочисленное переполнение.
  3. Определяемые пользователем типы, которые не определяют operator - (возможно, потому, что это не имеет смысла - можно определить порядок без определения понятия расстояния).

Я подозреваю, что этот список не является исчерпывающим.

Конечно, можно найти обходные пути как минимум для №1 и №2. Но цель operator <=> - инкапсулировать это уродство.

person Oliver Charlesworth    schedule 31.12.2017
comment
Обратите внимание, что выполнение трехстороннего сравнения строк (не просто const char*, а фактического класса строк) является разумной операцией. Вычитание двух строк - нет. - person Nicol Bolas; 31.12.2017

Здесь есть несколько содержательных ответов о разнице, но Херб Саттер в в его статье конкретно говорится:

‹=> предназначен для разработчиков типов: код пользователя (включая общий код) вне реализации оператора ‹=> почти никогда не должен вызывать ‹=> напрямую (как уже было обнаружено в качестве хорошей практики для других языков);

Таким образом, даже если не было никакой разницы, смысл оператора в другом: чтобы помочь разработчикам классов сгенерировать операторы сравнения.

Основное различие между оператором вычитания и оператором "космического корабля" (согласно предложению Саттера) заключается в том, что перегрузка operator- дает вам оператор вычитания, тогда как перегрузка operator<=>:

  • дает вам 6 основных операторов сравнения (даже если вы объявите оператор как default: нет кода для написания!);
  • объявляет, является ли ваш класс сопоставимым, сортируемым и является ли порядок полным или частичным (сильный / слабый в предложении Саттера);
  • позволяет проводить разнородные сравнения: вы можете перегрузить его, чтобы сравнить свой класс с любым другим типом.

Другие различия заключаются в возвращаемом значении: operator<=> вернет enum класса, класс указывает, является ли тип сортируемым и является ли сортировка сильной или слабой. Возвращаемое значение будет преобразовано в -1, 0 или 1 (хотя Саттер оставляет место для возвращаемого типа, чтобы также указывать расстояние, как это делает strcmp). В любом случае, предполагая возвращаемое значение -1, 0, 1, мы наконец получим настоящая сигнум-функция в C ++! (signum(x) == x<=>0)

person Cris Luengo    schedule 31.12.2017