Why is inequality tested as (!(a==b)) in a lot of C++ standard library code?

C++

C++ Problem Overview


This is the code from the C++ standard library remove code. Why is inequality tested as if (!(*first == val)) instead of 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;
 }

C++ Solutions


Solution 1 - C++

Because this means the only requirement on T is to implement an operator==. You could require T to have an operator!= but the general idea here is that you should put as little burden on the user of the template as possible and other templates do need operator==.

Solution 2 - C++

Most functions in STL work only with operator< or operator==. This requires the user only to implement these two operators (or sometimes at least one of them). For example std::set uses operator< (more precisely std::less which invokes operator< by default) and not operator> to manage ordering. The remove template in your example is a similar case - it uses only operator== and not operator!= so the operator!= doesn't need to be defined.

Solution 3 - C++

> This is the code from the C++ standard library remove code.

Wrong. It's not the C++ standard library remove code. It's one possible internal implementation of the C++ standard library remove function. The C++ standard does not prescribe actual code; it prescibes function prototypes and required behaviours.

In other words: From a strict language point of view, the code you are seeing does not exist. It may be from some header file that comes with your compiler's standard-library implementation. Note that the C++ standard does not even require those header files to exist. Files are just a convenient way for compiler implementors to meet the requirements for a line like #include <algorithm> (i.e. making std::remove and other functions available).

> Why is inequality tested as if (!(*first == val)) instead of if (*first != val) ?

Because only operator== is required by the function.

When it comes to operator overloading for custom types, the language allows you to do all kinds of weird things. You could very well create a class which has an overloaded operator== but no overloaded operator!=. Or even worse: You could overload operator!= but have it do completely unrelated things.

Consider this example:

#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());
  // ...
}

If std::remove used operator!=, then the result would be quite different.

Solution 4 - C++

Some good answers here. I just wanted to add a little note.

Like all good libraries, the standard library is designed with (at least) two very important principles in mind:

  1. Put the least amount of responsibility on users of your library that you can get away with. Part of this has to do with giving them the least amount of work to do when using your interface. (like defining as few operators as you can get away with). The other part of it has to do with not surprising them or requiring them to check error codes (so keep interfaces consistent and throw exceptions from <stdexcept> when things go wrong).

  2. Eliminate all logical redundancy. All comparisons can be deduced merely from operator<, so why demand that users define others? e.g:

    (a > b) is equivalent to (b < a)

    (a >= b) is equivalent to !(a < b)

    (a == b) is equivalent to !((a < b) || (b < a))

    and so on.

    Of course on this note, one might ask why unordered_map requires operator== (at least by default) rather than operator<. The answer is that in a hash table the only comparison we ever require is one for equality. Thus it is more logically consistent (i.e. makes more sense to the library user) to require them to to define an equality operator. Requiring an operator< would be confusing because it's not immediately obvious why you'd need it.

Solution 5 - C++

The EqualityComparable concept only requires that operator== be defined.

Consequently, any function that professes to work with types satisfying EqualityComparable cannot rely on the existence of operator!= for objects of that type. (unless there are additional requirements that imply the existence of operator!=).

Solution 6 - C++

> The most promising approach is to find a method of determining if > operator== can be called for a particular type, and then supporting it > only when it is available; in other situations, an exception would be > thrown. However, to date there is no known way to detect if an > arbitrary operator expression f == g is suitably defined. The best > solution known has the following undesirable qualities: > > * Fails at compile-time for objects where operator== is not accessible (e.g., because it is private). > * Fails at compile-time if calling operator== is ambiguous. > * Appears to be correct if the operator== declaration is correct, even though operator== may not compile.

From Boost FAQ : source

Knowing that requiring == implementation is a burden, you never want to create additional burden by requiring != implementation as well.

For me personally it's about SOLID (object-oriented design) L part - Liskov substitution principle : “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”. In this case it is the operator != that i can replace with == and boolean inverse in boolean logic.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAhmed NawarView Question on Stackoverflow
Solution 1 - C++Tom TannerView Answer on Stackoverflow
Solution 2 - C++Lukáš BednaříkView Answer on Stackoverflow
Solution 3 - C++Christian HacklView Answer on Stackoverflow
Solution 4 - C++Richard HodgesView Answer on Stackoverflow
Solution 5 - C++user1084944View Answer on Stackoverflow
Solution 6 - C++MargusView Answer on Stackoverflow