Чому нерівність перевіряється як (! (A == b)) у великій кількості стандартного коду бібліотеки C ++?


142

Це код із стандартного removeкоду бібліотеки C ++ . Чому нерівність перевіряється як if (!(*first == val))замість if (*first != val)?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }

2
@BeyelerStudios, ймовірно, правильний. Це також часто при впровадженні operator!=. Просто використовуйте operator==реалізацію:bool operator!=(const Foo& other) { return !(*this == other); }
simon

1
Насправді я виправляю своє твердження: згадування посилань видаляє всі елементи, які є рівними за значенням, тому, operator==як очікується, тут будуть використані ...
BeyelerStudios

О, і constв моєму попередньому коментарі також має бути приклад, але ви розумієте це. (Занадто пізно для редагування)
simon

Причина цього пов'язана з іншим питанням (на яке в основному можна відповісти «Ні, не обов’язково»), і концепцією буття, про EqualityComparableяку Хуркіл згадував у своїй відповіді .
Marco13

Відповіді:


144

Тому що це означає, що єдиною вимогою на T є реалізація operator==. Ви можете зажадати від T, operator!=але тут загальна ідея полягає в тому, що ви повинні накласти якомога менше навантаження на користувача шаблону та інших шаблонів operator==.


13
шаблон <клас T> вбудований оператор bool! = <T a, T b> {return! (a == b); }
Джошуа

8
Чи був би сценарій, коли компілятор не міг би обмінятися всіма примірниками =! до! (==)? Чому б це не було дією за замовчуванням для компілятора?
Айдан Гомес

20
@AidanGomez Для кращого або гіршого випадку ви можете перевантажувати операторів, щоб робити все, що завгодно. Це не повинно мати сенсу або бути послідовним.
Ніл Кірк

10
x != yне визначено таким, як !(x == y). Що робити, якщо ці оператори повернуть дерево розбору вбудованого DSL?
Бріс М. Демпсі

7
@Joshua Це погано руйнується при спробі використання SFINAE для виявлення, чи !=підтримується (неправильно повернути true, навіть якщо operator==він не підтримується!). Я також переживаю, що це призведе !=до неоднозначності деяких застосувань .

36

Більшість функцій STL працюють лише з operator<або operator==. Це вимагає від користувача лише впровадження цих двох операторів (або іноді хоча б одного з них). Наприклад, std::setвикористовує operator<(точніше, std::lessякий викликає operator<за замовчуванням), а не operator>керувати замовленнями. removeШаблон у вашому прикладі подібний випадок - він використовує тільки operator==і не operator!=так operator!=не повинні бути визначені.


2
Функції використовуються не operator<безпосередньо, а натомість використовують std::less, що, у свою чергу, за замовчуванням operator<.
Крістіан Хакл

1
Насправді, схоже, що стандартні функції алгоритму, на відміну від, наприклад std::set, дійсно використовують operator<безпосередньо. Дивно ...
Крістіан Хакл

1
Ці функції не використовуються std::equal_to, вони використовуються, operator==як зазначено в питанні. Ситуація з std::lessаналогічною. Ну, можливо, std::setце не найкращий приклад.
Lukáš Bednařík

2
@ChristianHackl std::equal_toі std::lessвикористовуються як параметри шаблону за замовчуванням, де компаратор береться за параметр. operator==і operator<використовуються безпосередньо там, де тип потрібен для задоволення рівності, порівнянного та суворого слабкого впорядкування відповідно, наприклад, ітераторів та ітераторів випадкового доступу.
Ян Худек

28

Це код із коду видалення зі стандартної бібліотеки C ++.

Неправильно. Це не C ++ стандартної бібліотеки коду. Це одна можлива внутрішня реалізація стандартної функції бібліотеки C ++ . Стандарт C ++ не прописує фактичний код; Це передбачає функціонування прототипів і вимагає поведінки.removeremove

Іншими словами: З суворої мовної точки зору код, який ви бачите , не існує . Це може бути з якогось файла заголовка, який постачається зі стандартною реалізацією бібліотеки вашого компілятора. Зауважте, що стандарт C ++ навіть не вимагає наявності цих заголовкових файлів . Файли - це просто зручний спосіб, щоб виконавці-компілятори відповідали вимогам для такої лінії #include <algorithm>(тобто надання std::removeта доступність інших функцій).

Чому нерівність перевіряється як if (!(*first == val))замість if (*first != val)?

Тому що operator==вимагає лише функція.

Якщо мова йде про перевантаження оператора для користувацьких типів, мова дозволяє робити всі види дивних речей. Ви могли б дуже добре створити клас, який перевантажений, operator==але не перевантажений operator!=. Або ще гірше: ви можете перевантажувати, operator!=але змушувати це робити абсолютно не пов'язані між собою речі.

Розглянемо цей приклад:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

Якщо std::removeвикористовувати operator!=, то результат був би зовсім іншим.


1
Інша річ, яку слід врахувати, - це те, що може бути можливим і те, a==bі a!=bповернути помилкове. Хоча не завжди може бути зрозумілим, чи буде така ситуація більш значущо розцінюватися як "рівна" чи "не рівна", функція, яка визначає рівність виключно з точки зору оператора "==", повинна вважати їх "не рівними" ", незалежно від того, яка поведінка мала б більше сенсу [якби я мав свої барабани, очікувались, що всі типи операторів булеутворюючих" == "та"! = "поводяться послідовно, але факт, що IEEE-754 мандати порушили рівність оператори ускладнюють таке очікування].
supercat

18
"З суворої мовної точки зору, код, який ви бачите, не існує." - коли точка зору говорить про те, що чогось не існує, але ви насправді дивитесь на цю річ, то точка зору помилкова. Насправді стандарт не говорить про те, що код не існує, він просто не говорить, що він існує :-)
Стів Джессоп

@SteveJessop: Ви можете "замінити вираз" із суворої мовної точки зору "на щось на кшталт" строго на мовному рівні ". Справа в тому, що код, розміщений ОП, не стосується мовного стандарту.
Крістіан Хакл

2
@supercat: IEEE-754 робить ==і !=поводиться послідовно, хоча я завжди вважав, що всі шість відносин повинні оцінювати, falseколи хоча б один операнд NaN.
Бен Войгт

@BenVoigt: Ага, це правильно. Це змушує двох операторів вести себе однаково порушеним способом таким чином, що вони узгоджуються один з одним, але все-таки вдається порушити всі інші нормальні аксіоми, пов'язані з еквівалентністю (наприклад, вони не підтримують ні a == a, ні гарантію, що операції виконували за рівних значень дадуть рівні результати).
supercat

15

Тут є кілька хороших відповідей. Я просто хотів додати невеличку замітку.

Як і всі хороші бібліотеки, стандартна бібліотека розроблена з (принаймні) двома дуже важливими принципами:

  1. Покладіть найменшу відповідальність на користувачів вашої бібліотеки, від яких ви можете піти. Частина цього пов'язана з тим, щоб приділити їм найменший обсяг роботи при використанні вашого інтерфейсу. (наприклад, визначити якомога менше операторів). Інша частина цього стосується того, щоб не дивувати їх чи вимагати від них перевірки кодів помилок (тому дотримуйтесь послідовності інтерфейсів та кидайте винятки, <stdexcept>коли справи йдуть не так).

  2. Усуньте всі логічні надмірності . Усі порівняння можна вивести лише з того operator<, чому ж вимагати, щоб користувачі визначали інших? наприклад:

    (a> b) еквівалентно (b <a)

    (a> = b) еквівалентно! (a <b)

    (a == b) еквівалентно! ((a <b) || (b <a))

    і так далі.

    Звичайно, у цій примітці можна запитати, чому unordered_mapпотрібно operator==(принаймні за замовчуванням), а не operator<. Відповідь полягає в тому, що в хеш-таблиці єдине порівняння, яке ми потребуємо, - це рівність. Таким чином, більш логічно послідовно (тобто має більше сенсу користувач бібліотеки) вимагати від них визначення оператора рівності. Потрібна запит operator<буде заплутаною, оскільки не відразу зрозуміло, для чого вона вам потрібна.


10
Стосовно вашого другого пункту: Існують типи, які логічно можна порівняти за рівність, навіть якщо логічного порядку не існує або встановлення порядку було б дуже штучним. Наприклад, червоний червоний, а червоний - не зелений, але червоний по суті менше, ніж зелений?
Крістіан Хакл

1
Повністю згоден. Не можна зберігати ці елементи в упорядкованому контейнері, оскільки немає логічного порядку. Вони можуть бути більш доцільними для зберігання в не упорядкованому контейнері, який вимагає operator==hash).
Річард Ходжес

3. Перевантажуйте якнайменше стандартних операторів. Це ще одна причина, яку вони реалізували !(a==b). Оскільки безперебійна перевантаження операторів може легко призвести до того, що програма C ++ повністю заплутається (плюс, змусити програміста зійти з розуму, оскільки налагодження його коду може стати місією неможливою, оскільки пошук винуватця конкретної помилки нагадує одісею).
синтаксичний помилок

!((a < b) || (b < a))використовує одного менш оператора bool, тож, мабуть, швидше
Філіп Хаглунд,

1
Насправді вони цього не роблять. У мові складання всі порівняння реалізуються як віднімання з подальшим тестуванням перенесення та нульових бітів у регістрі прапорів. Все інше - це лише синтаксичний цукор.
Річард Ходжес

8

EqualityComparableПоняття тільки вимагає , щоб operator==визначити.

Отже, будь-яка функція, яка спричиняє роботу з типами, що задовольняють, EqualityComparable не може покладатися на існування operator!=для об'єктів цього типу. (якщо немає додаткових вимог, які передбачають існування operator!=).


1

Найбільш перспективним підходом є пошук способу визначення, чи може оператор == викликати певний тип, а потім підтримувати його лише тоді, коли він доступний; в інших ситуаціях буде викинуто виняток. Однак на сьогоднішній день не існує відомого способу виявити, чи довільно визначений довільний вираз оператора f == g. Найкраще відоме рішення має такі небажані якості:

  • Помилка під час компіляції для об'єктів, де оператор == недоступний (наприклад, тому що він приватний).
  • Помилка під час компіляції, якщо виклик оператора == неоднозначний.
  • Здається, що це правильне, якщо оператор == декларація правильна, навіть якщо оператор == може не компілюватися.

З Boost FAQ: джерело

Знаючи, що вимагати ==впровадження є тягарем , ви ніколи не хочете створювати додатковий тягар, вимагаючи також !=реалізації.

Для мене особисто йдеться про SOLID (об'єктно-орієнтований дизайн) L частина - принцип заміни Ліскова: "Об'єкти в програмі повинні бути замінними екземплярами їх підтипів, не змінюючи правильність цієї програми". У цьому випадку оператор ! = Що я можу замінити на == та булеву зворотну в логічній логіці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.