Почему неравенство проверяется как (! (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); }
Симон

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

О, и constв моем предыдущем комментарии также должен быть пример, но вы поняли. (Слишком поздно для редактирования)
Симон

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

Ответы:


144

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


13
template <class 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, не лучший пример.
Лукаш Беднаржик

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вернуть false. Хотя не всегда может быть ясно, будет ли такая ситуация более осмысленно рассматриваться как «равная» или «не равная», функция, которая определяет равенство исключительно в терминах оператора «==», должна рассматривать их как «неравные». ", независимо от того, какое поведение имело бы больше смысла [если бы у меня были мои детекторы, все типы должны были бы обеспечивать логически приводящие операторы" == "и"! = ", ведущие себя согласованно, но тот факт, что IEEE-754 требует нарушенного равенства операторы затруднят такое ожидание.
суперкат

18
«С точки зрения языка, код, который вы видите, не существует». - когда точка зрения говорит, что чего-то не существует, но вы на самом деле смотрите на это, тогда точка зрения неверна. Фактически, стандарт не говорит, что код не существует, он просто не говорит, что он существует :-)
Стив Джессоп

@SteveJessop: Вы можете заменить выражение «со строгой языковой точки зрения» чем-то вроде «строго на уровне языка». Дело в том, что код, публикуемый ФП, не касается языкового стандарта.
Кристиан Хакл,

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

@BenVoigt: Ах, это правильно. Это заставляет два оператора вести себя одинаково, так что они согласуются друг с другом, но все же могут нарушать все другие нормальные аксиомы, связанные с эквивалентностью (например, они не поддерживают ни == a, ни гарантию того, что выполненные операции на равных значениях даст равные результаты).
суперкат

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. Лучшее из известных решений имеет следующие нежелательные качества:

  • Сбой во время компиляции для объектов, где operator == недоступен (например, потому что он закрытый).
  • Сбой во время компиляции, если вызов оператора == является неоднозначным.
  • Представляется правильным, если объявление оператора == правильное, даже если оператор == может не скомпилироваться.

Из Boost FAQ: источник

Зная, что требовать ==внедрения - это бремя , вы никогда не захотите создавать дополнительное бремя, требуя также и !=реализации.

Лично для меня это L-часть SOLID (объектно-ориентированное проектирование) - принцип подстановки Лискова: «объекты в программе должны заменяться экземплярами их подтипов без изменения правильности этой программы». В этом случае это оператор ! = , Который я могу заменить на == и логическое обратное в логической логике.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.