Подтвердить что ты не робот

Перегрузки операторов равенства: Is (x!= Y) == (! (X == y))?

Гарантирует ли стандарт C++, что (x!=y) всегда имеет то же значение истинности, что и !(x==y)?


Я знаю, что здесь задействовано много тонкостей: операторы == и != Могут быть перегружены. Они могут быть перегружены, чтобы иметь разные типы возврата (которые должны быть неявно конвертируемыми в bool). Даже то ! -operator может быть перегружен в типе возврата. Вот почему я хрупко упомянул "ценность истины" выше, но попытался развить ее дальше, используя неявное преобразование в bool и пытаясь устранить возможные неясности:

bool ne = (x!=y);
bool e = (x==y);
bool result = (ne == (!e));

result гарантированно будет true здесь?

Стандарт C++ определяет операторы равенства в разделе 5.10, но в основном, кажется, определяет их синтаксически (и некоторую семантику в отношении сравнения указателей). Концепция EqualityComparable существует, но нет специального утверждения об отношении ее оператора == к оператору !=.

Существуют соответствующие документы от C++ рабочих групп, в которых говорится, что...

Важно, чтобы равные/неравные [...] вели себя как булевы отрицания друг друга. В конце концов, мир не будет иметь смысла, если оба оператора ==() и оператор! =() Вернули false! Таким образом, эти операторы часто используются в терминах друг друга.

Однако это только отражает Common Sense ™ и не указывает, что они должны быть реализованы таким образом.


Немного предыстории: я просто пытаюсь написать функцию, которая проверяет, равны ли два значения (неизвестного типа), и выводить сообщение об ошибке, если это не так.Я хотел бы сказать, что необходимая концепция здесь заключается в том, что типы являются EqualityComparable.Но для этого все равно придется писать if (!(x==y)) {...} и не может писать if (x!=y) {...}, потому что для этого будет использоваться другой оператор, который вообще не покрывается понятием EqualityComparable, и который может быть даже перегружен по-другому...


Я знаю, что программист в основном может делать все, что он хочет в своих пользовательских перегрузках. Мне просто интересно, действительно ли ему разрешено делать все, или есть правила, налагаемые стандартом. Может быть, одно из этих тонких утверждений, предполагающих, что отклонение от обычной реализации вызывает неопределенное поведение, подобное тому, которое НатанОливер упомянул в комментарии, но которое, похоже, относится только к определенным типам. Например, в стандарте прямо указано, что для типов контейнеров a!=b эквивалентно !(a==b) (раздел 23.2.1, таблица 95, "Требования к контейнерам").

Но для общих пользовательских типов в настоящее время кажется, что таких требований нет. Вопрос помечен как language-lawyer, потому что я надеялся на определенное утверждение/ссылку, но я знаю, что это может быть почти невозможно: хотя можно указать на раздел, где говорится, что операторы должны быть отрицаниями друг друга, один вряд ли можно доказать, что ни одна из ~ 1500 страниц стандарта не говорит что-то подобное...

Сомневаюсь, и, если не будет дальнейших подсказок, я позже проголосую/приму соответствующие ответы, а пока предположим, что для сравнения неравенства для типов EqualityComparable нужно сделать, if (!(x==y)) на безопасной стороне.

4b9b3361

Ответ 1

Гарантирует ли стандарт C++, что (x!=y) всегда имеет то же значение истинности, что и !(x==y)?

Нет, это не так. Абсолютно ничто не мешает мне писать:

struct Broken {
    bool operator==(const Broken& ) const { return true; }
    bool operator!=(const Broken& ) const { return true; }
};

Broken x, y;

Это совершенно правильно сформированный код. Семантически, он сломался (как можно предположить из названия), но в этом нет ничего плохого с точки зрения функциональности чистого кода C++.

Стандарт также четко указывает, что это нормально в [over.oper]/7:

Тождества среди определенных предопределенных операторов, применяемых к базовым типам (например, ++a ≡ a+=1), не обязательно должны выполняться для операторских функций. Некоторые предопределенные операторы, такие как +=, требуют, чтобы операнд был lvalue при применении к базовым типам; это не требуется операторскими функциями.

В том же духе, ничто в стандарте C++ не гарантирует, что operator< фактически реализует действительный порядок (или что x<y <==> !(x>=y) и т.д.). Некоторые реализации стандартных библиотек фактически добавят инструментарий, чтобы попытаться отладить его для вас в упорядоченных контейнерах, но это всего лишь проблема качества реализации, а не решение, основанное на стандартах.


Существуют библиотечные решения, такие как Boost.Operators, чтобы хотя бы немного упростить это для программиста:

struct Fixed : equality_comparable<Fixed> {
    bool operator==(const Fixed&) const;
    // a consistent operator!= is provided for you
};

В C++ 14, Fixed больше не является агрегатом с базовым классом. Однако в C++ 17 это снова совокупность (в виде P0017).


С принятием P1185 для C++ 20 решение для библиотеки фактически стало языковым решением - вам просто нужно написать это:

struct Fixed {
    bool operator==(Fixed const&) const;
};

bool ne(Fixed const& x, Fixed const& y) {
    return x != y;
}

Тело ne() становится допустимым выражением, которое оценивается как !x.operator==(y) - так что вам не нужно беспокоиться о том, чтобы сохранить сравнение двух строк, и не полагаться на решение библиотеки, чтобы помочь.

Ответ 2

В общем, я не думаю, что вы можете положиться на него, потому что он не всегда имеет смысл для operator == и operator!= всегда соответствовать, поэтому я не вижу, как стандарт может когда-либо потребоваться.

Например, рассматривают встроенные типы с плавающей запятой, такие как удваивает, для которых NaNs всегда сравнивают false, поэтому оператор == и оператор!= могут возвращаться false одновременно. (Edit: Ой, это неправильно, см. Комментарий hvd.)

В результате, если я пишу новый класс с семантикой с плавающей запятой (возможно, true_long_double), я должен реализовать такое же поведение, чтобы оно соответствовало примитивным типам, поэтому мой operator== должен был бы вести себя то же самое и сравнить два NaN как ложные, хотя operator!= также сравнивает их как false.

Это может произойти и в других обстоятельствах. Например, если я писал класс для представления значения NULL для базы данных, я мог бы запустить ту же проблему, потому что все сравнения с NULL базы данных являются ложными. Я мог бы реализовать эту логику в моем С++-коде, чтобы иметь ту же семантику, что и база данных.

На практике, однако, для вашего случая использования, возможно, не стоит беспокоиться об этих крайних случаях. Просто задокументируйте, что ваша функция сравнивает объекты с помощью operator== (or operator !=) и оставьте это при этом.

Ответ 3

Нет. Вы можете написать перегрузки оператора для == и !=, которые делают все, что вы пожелаете. Вероятно, это была бы плохая идея, но определение С++ не ограничивает эти операторы друг от друга логическими противоположностями.