Why Is Operator!= Removed in C++20 for Many Standard Library Types

Why is operator!= removed in C++20 for many standard library types?

In C++20 the way that the relational operators work was changed, notably with the introduction of the spaceship <=> operator. In particular, If you only provide operator==, then a != b is rewritten to !(a == b).

From [over.match.oper]/3.4:

The rewritten candidate set is determined as follows:

  • For the relational ([expr.rel]) operators, the rewritten candidates include all non-rewritten candidates for the expression x <=> y.
  • For the relational ([expr.rel]) and three-way comparison ([expr.spaceship]) operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y <=> x.
  • For the != operator ([expr.eq]), the rewritten candidates include all non-rewritten candidates for the expression x == y.
  • For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.
  • For all other operators, the rewritten candidate set is empty.

And [over.match.oper]/9:

If a rewritten operator== candidate is selected by overload resolution for an operator @, its return type shall be cv bool, and x @ y is interpreted as:

  • if @ is != and the selected candidate is a synthesized candidate with reversed order of parameters, !(y == x),
  • otherwise, if @ is !=, !(x == y),
  • otherwise (when @ is ==), y == x,

in each case using the selected rewritten operator== candidate.

As such, an explicit overload for operator!= is no longer necessary. The removal of the operator has not changed comparison semantics.

All containers have had their operator!= removed, as far as I can tell (check e.g. the vector synopsis). The only exceptions are the container adaptors std::queue and std::stack: my guess is that it is to preserve backwards compatibility when used with third-party containers, in case the equality operators are not symmetric.

Why have comparison operators been removed from standard library containers in C++ 20?

If you continue to browse on the reference site a little, you might come to the section on default comparisons, which simply states that:

In brief, a class that defines operator<=> automatically gets compiler-generated operators <, <=, >, and >=.

So, if the "spaceship" operator exists for a class, the compiler will auto-generate the remaining comparison operators using the result of the <=> operator.

Note that the == operator is not generated (even though it should be possible), but std::vector keeps an overload of operator==.


As for:

will C++ 20 start giving errors on older codes ?

No, it will not.

When you build with a C++20 compiler, the standard library used with it should also be made for C++20 and thus implement the <=> operator, which will then be used as explained above.

However, if you use a C++20 compiler to build with an older standard library, that older standard library will still have the older comparison operators implemented.

Why will std::rel_ops::operators be deprecated in C++20?

In C++20, you get three-way comparison (operator <=>), which automatically "generates" default comparisons if provided:

struct A {
// You only need to implement a single operator.
std::strong_ordering operator<=>(const A&) const;
};

// Compiler generates 4 relational operators (you need to default the
// three-way comparison operator to get == and !=).
A to1, to2;
if (to1 > to2) { /* ... */ } // ok
if (to1 <= to2) { /* ... */ } // ok, single call to <=>

There are multiple advantages of the three-way comparison over std::rel_ops, which is probably why std::rel_ops operators are deprecated. On top of my head:

  • It is more versatile, since, depending on the return type of operator<=> (std::strong_ordering, std::weak_ordering, ...), only relevant operators are generated. See the <compare> header for more information.

  • You do not bring a bunch of templated operator overloads by doing using namespace std::rel_ops.

  • You can ask the compiler to generate the three-way operator for you by defaulting it (auto operator<=>(A const&) = default) — This will basically generate a lexicographic comparison of base classes and non-static data members, plus it will deduce the right type of ordering if the return type is auto.

Why are standard library types accessible inside `std` despite being nested in implementation-defined namespaces?

The c++config in libstdc++ that defines these macros also initially declares the version namespace as inline here:

// Defined if inline namespaces are used for versioning.
#define _GLIBCXX_INLINE_VERSION

// Inline namespace for symbol versioning.
#if _GLIBCXX_INLINE_VERSION
# define _GLIBCXX_BEGIN_NAMESPACE_VERSION namespace __8 {
# define _GLIBCXX_END_NAMESPACE_VERSION }

namespace std
{
inline _GLIBCXX_BEGIN_NAMESPACE_VERSION
#if __cplusplus >= 201402L
inline namespace literals {
inline namespace chrono_literals { }
inline namespace complex_literals { }
inline namespace string_literals { }
#if __cplusplus > 201402L
inline namespace string_view_literals { }
#endif // C++17
}
#endif // C++14
_GLIBCXX_END_NAMESPACE_VERSION
}

And once a namespace is declared inline, it's always inline. For instance:

namespace A {
inline namespace B {
struct X { };
}
}

namespace A {
namespace B {
struct Y { };
}
}

A::X x; // ok
A::Y y; // still ok

clang warns on this even if gcc doesn't, but also it's a system header so even if gcc wanred on it it wouldn't matter.

Still, not sure why not just add inline to the macro definition. We're probably not at the point where the additional 7 characters per header is a significant detriment to compile time?

C++20: Automatically generated operators are unreferencable in derived classes?

As I understand, since C++20, comparison operators are automatically generated by the compiler. However, I have encountered an interesting problem with this automatic generation of operators.

This is not correct. The comparison operators are not generated. Comparison expressions are rewritten.

That is, given:

struct X {
int i;
bool operator==(X const&) const = default;
};

X{2} == X{3} is straightforwardly valid, invokes the defaulted operator==, which does member-wise comparison, and thus yields false.

X{2} != X{3} is also a valid expression, but it does not invoke anything named operator!=. There is no such function. Instead, it evaluates as !(X{2} == X{3}), yielding true. Despite X{2} != X{3} being a valid expression, there is nothing named operator!= anywhere here, so you cannot reference anything with that name.

So naturally, I use using declarations to use member functions from the base class. However, the compiler complains that operator!= is missing!

In C++20, we almost never actually need an operator!=, so nearly all of them were removed from the standard library specification, and the standard libraries likely went through and #ifdef-ed them out. Less code to have to parse. The inequality expressions still work, due to the operator rewrites, but there isn't anything named operator!= anymore.

So you can't using it.

But the same reason that the standard library doesn't need operator!= anymore also applies to you - you don't need it either.


Note further that there's no guarantee that using Base::operator!=; worked even in C++17, since there's no obligation for operator!= to have been written as a member function. It could have been written as a free function, and then this wouldn't have worked anyway, even though there would be a function named operator!=... just somewhere else.

!= auto generated from == in C++20?

!= auto generated from == in C++20?

Is this standard behavior in C++20?

Yes. operator!= is auto generated from operator== in C++20.

Furthermore, all four relational operators are generated if you define operator<=>, and all of the comparison operators are generated if you define operator<=> as defaulted.

What you want to do in most cases:

struct example
{
std::string a;
int b;

auto operator<=>(const example&) const = default;
};

Why don't C++ compilers define operator== and operator!=?

The compiler wouldn't know whether you wanted a pointer comparison or a deep (internal) comparison.

It's safer to just not implement it and let the programmer do that themselves. Then they can make all the assumptions they like.

Breaking change in C++20 or regression in clang-trunk/gcc-trunk when overloading equality comparison with non-Boolean return value?

The Eigen issue appears to reduce to the following:

using Scalar = double;

template<class Derived>
struct Base {
friend inline int operator==(const Scalar&, const Derived&) { return 1; }
int operator!=(const Scalar&) const;
};

struct X : Base<X> {};

int main() {
X{} != 0.0;
}

The two candidates for the expression are

  1. the rewritten candidate from operator==(const Scalar&, const Derived&)
  2. Base<X>::operator!=(const Scalar&) const

Per [over.match.funcs]/4, as operator!= was not imported into the scope of X by a using-declaration, the type of the implicit object parameter for #2 is const Base<X>&. As a result, #1 has a better implicit conversion sequence for that argument (exact match, rather than derived-to-base conversion). Selecting #1 then renders the program ill-formed.

Possible fixes:

  • Add using Base::operator!=; to Derived, or
  • Change the operator== to take a const Base& instead of a const Derived&.

Why must I provide 'operator ==' when 'operator =' is enough?

Why must I provide operator== when operator<=> is enough?

Well, mainly because it's not enough :-)

Equality and ordering are different buckets when it comes time for C++ to rewrite your statements:



Leave a reply



Submit