Why Don't C++ Compilers Define Operator== and Operator!=

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.

Why doesn't C++ use operator== instead of operator!= automatically

Because operator== does not necessarily mean the opposite of operator!=.

I cannot think of any instance where operator== would not mean !operator!=, but they are separate operators. One of the most liberating and, at times, most frustrating things about C++ is that C++ applies a minimal set of restrictions about how you can write your code. If you have an instance where operator== is not the opposite of operator!=, then you should be able to express that in C++. And, in fact, you can.

You take the good with the bad in C++. You may consider this to be in the set of "the bad".

Bear in mind that in the vast majority of cases, it is trivial to correctly implement operator!= in terms of operator==.

bool Gizmo::operator!=(const Gizmo& rhs) const
{
return !operator==(rhs);
}

Why must we define both == and != in C#?

I can't speak for the language designers, but from what I can reason on, it seems like it was intentional, proper design decision.

Looking at this basic F# code, you can compile this into a working library. This is legal code for F#, and only overloads the equality operator, not the inequality:

module Module1

type Foo() =
let mutable myInternalValue = 0
member this.Prop
with get () = myInternalValue
and set (value) = myInternalValue <- value

static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
//static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

This does exactly what it looks like. It creates an equality comparer on == only, and checks to see if the internal values of the class are equal.

While you can't create a class like this in C#, you can use one that was compiled for .NET. It's obvious it will use our overloaded operator for == So, what does the runtime use for !=?

The C# EMCA standard has a whole bunch of rules (section 14.9) explaining how to determine which operator to use when evaluating equality. To put it overly-simplified and thus not perfectly accurate, if the types that are being compared are of the same type and there is an overloaded equality operator present, it will use that overload and not the standard reference equality operator inherited from Object. It is no surprise, then, that if only one of the operators is present, it will use the default reference equality operator, that all objects have, there is not an overload for it.1

Knowing that this is the case, the real question is: Why was this designed in this way and why doesn't the compiler figure it out on its own? A lot people are saying this wasn't a design decision, but I like to think it was thought out this way, especially regarding the fact all objects have a default equality operator.

So, why doesn't the compiler automagically create the != operator? I can't know for sure unless someone from Microsoft confirms this, but this is what I can determine from reasoning on the facts.


To prevent unexpected behavior

Perhaps I want to do a value comparison on == to test equality. However, when it came to != I didn't care at all if the values were equal unless the reference was equal, because for my program to consider them equal, I only care if the references match. After all, this is actually outlined as default behavior of the C# (if both operators were not overloaded, as would be in case of some .net libraries written in another language). If the compiler was adding in code automatically, I could no longer rely on the compiler to output code that should is compliant. The compiler should not write hidden code that changes the behavior of yours, especially when the code you've written is within standards of both C# and the CLI.

In terms of it forcing you to overload it, instead of going to the default behavior, I can only firmly say that it is in the standard (EMCA-334 17.9.2)2. The standard does not specify why. I believe this is due to the fact that C# borrows much behavior from C++. See below for more on this.


When you override != and ==, you do not have to return bool.

This is another likely reason. In C#, this function:

public static int operator ==(MyClass a, MyClass b) { return 0; }

is as valid as this one:

public static bool operator ==(MyClass a, MyClass b) { return true; }

If you're returning something other than bool, the compiler cannot automatically infer an opposite type. Furthermore, in the case where your operator does return bool, it just doesn't make sense for them create generate code that would only exist in that one specific case or, as I said above, code that hides the default behavior of the CLR.


C# borrows much from C++3

When C# was introduced, there was an article in MSDN magazine that wrote, talking about C#:

Many developers wish there was a language that was easy to write, read, and maintain like Visual Basic, but that still provided the power and flexibility of C++.

Yes the design goal for C# was to give nearly the same amount of power as C++, sacrificing only a little for conveniences like rigid type-safety and garbage-collection. C# was strongly modeled after C++.

You may not be surprised to learn that in C++, the equality operators do not have to return bool, as shown in this example program

Now, C++ does not directly require you to overload the complementary operator. If your compiled the code in the example program, you will see it runs with no errors. However, if you tried adding the line:

cout << (a != b);

you will get

compiler error C2678 (MSVC) : binary '!=' : no operator found which takes a left-hand operand of type 'Test' (or there is no acceptable conversion)`.

So, while C++ itself doesn't require you to overload in pairs, it will not let you use an equality operator that you haven't overloaded on a custom class. It's valid in .NET, because all objects have a default one; C++ does not.


1. As a side note, the C# standard still requires you to overload the pair of operators if you want to overload either one. This is a part of the standard and not simply the compiler. However, the same rules regarding the determination of which operator to call apply when you're accessing a .net library written in another language that doesn't have the same requirements.

2. EMCA-334 (pdf) (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf)

3. And Java, but that's really not the point here

C++ is operator!= automatically provided when operator== defined

The operator != is not automatically provided for you. You may want to read about rel_ops namespace if you want such automation. Essentially you can say

using namespace std::rel_ops;

before using operator !=.

non-defaulted operator = doesn't generate == and != in C++20

This is by design.

[class.compare.default] (emphasis mine)

3 If the class definition does not explicitly declare an ==
operator function, but declares a defaulted three-way comparison
operator
function, an == operator function is declared implicitly
with the same access as the three-way comparison operator function.
The implicitly-declared == operator for a class X is an inline
member and is defined as defaulted in the definition of X.

Only a defaulted <=> allows a synthesized == to exist. The rationale is that classes like std::vector should not use a non-defaulted <=> for equality tests. Using <=> for == is not the most efficient way to compare vectors. <=> must give the exact ordering, whereas == may bail early by comparing sizes first.

If a class does something special in its three-way comparison, it will likely need to do something special in its ==. Thus, instead of generating a potentially non-sensible default, the language leaves it up to the programmer.

Should I inline operators == and != in case when operator!=() uses negation of operator==()

You should use link-time optimization (LTO) so either of them can fully inline into the call-site, especially when it's near-trivial like this.

But if you don't want to use LTO for cross-file inlining, then yes it would be a good idea to put the operator != return !(*this == rhs); definition inside the class definition in the .h) so it's visible to every caller and can inline there into files that just included the .h. Then the asm for callers will call the same operator== definition but use the result the opposite way. e.g. test al,al / jnz instead of jz if you're branching on the result.

If you don't use LTO and don't make the definition visible for compile-time inlining, the best that will happen is the compiler will inline operator== into the operator!= stand-alone definition when compiling that one .cpp. Then you have two similar-sized functions in the machine code that differ only by one boolean inversion. Users of these functions (from other files) will call one or the other, so they're both taking up space in your I-cache / code footprint.



Example

https://godbolt.org/z/e88nGj

// Something.h
struct Something {
int val;

bool operator==(const Something& rhs);
bool operator!=(const Something& rhs) { return !(*this == rhs); }
};
// simulated #include for one-file demo purposes

// Some other .cpp file, operator== definition not visible.
int foo(Something &a, Something &b)
{
if (a != b) {
return a.val;
} else {
return b.val;
}
}

GCC -O3 for x86-64 (Godbolt) compiles as follows:

foo(Something&, Something&):
push rbp
mov rbp, rsi
push rbx
mov rbx, rdi # save the pointers in call-preserved regs
sub rsp, 8
call Something::operator==(Something const&)
test al, al # set FLAGS from the bool retval
cmovne rbx, rbp # select the right pointer
mov eax, DWORD PTR [rbx] # and load from it

add rsp, 8 # epilogue
pop rbx
pop rbp
ret

Notice that this code calls Something::operator== which couldn't inline at compile time (it could at link time with LTO). It just uses cmovne instead of cmove if it had called an actual separate operator!=.

The operator!= inlined to literally zero extra cost, and all calls to either function use the same stand-alone definition, saving code footprint. Good for performance especially if you have code that uses both operators enough for it to stay hot in cache.

Of course, letting operator== inline as well would give significant savings when the class is just an int; no call at all is often a lot better because there's no need to preserve registers around something.

(Of course in this case my example is too trivial: if they are equal, then it can still return a.val because it knows that's the same as b.val. So if you uncomment the operator== definition in the Godbolt link, foo compiles to mov eax, DWORD PTR [rdi] / ret, never even touching b.)

Is this a safe way to implement a generic operator== and operator ?

Never do this unless you're 100% sure about the memory layout, compiler behavior, and you really don't care portability, and you really want to gain the efficiency

SOURCE

Checking for three-way-comparison operator support at compile time

This is what feature-test macros are for. There is a standing document that defines all the macros and their values; those are the macros and values that you check for, that all vendors agree to abide by.

Three-way-comparison specifically is a bit tricky because this is a feature that requires both language and library support. There is a language-level feature test macro, but it isn't intended for you (the user), it's intended for the standard library author to conditionally provide that functionality.

So what you really have to do this is this:

#if __has_include(<compare>)
# include <compare>
# if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907
# define SPACESHIP_OPERATOR_IS_SUPPORTED 1
# endif
#endif

And now in the rest of your code you can check #ifdef SPACESHIP_OPERATOR_IS_SUPPORTED to conditionally provide <=>:

#ifdef SPACESHIP_OPERATOR_IS_SUPPORTED
bool operator==(const thing<N> &) const = default;
std::strong_ordering operator<=>(const thing<N> &) const = default;

template <int R> bool operator==(const thing<R> &) const = delete;
template <int R> std::strong_ordering operator<=>(const thing<R> &) const = delete;
#else
bool operator==(const thing<N> &) const { return true; }
bool operator!=(const thing<N> &) const { return false; }
bool operator< (const thing<N> &) const { return false; }
bool operator> (const thing<N> &) const { return false; }
bool operator<=(const thing<N> &) const { return true; }
bool operator>=(const thing<N> &) const { return true; }

template <int R> bool operator==(const thing<R> &) const = delete;
template <int R> bool operator!=(const thing<R> &) const = delete;
template <int R> bool operator< (const thing<R> &) const = delete;
template <int R> bool operator> (const thing<R> &) const = delete;
template <int R> bool operator<=(const thing<R> &) const = delete;
template <int R> bool operator>=(const thing<R> &) const = delete;
#endif

You don't need to provide both defaulted <=> and all the relational operators. That's why we have <=>: so you can write <=> by itself. You still need to provide operator== but only because you're doing something special in needing to delete <=>.

incorrect function call on overload operator

There are two new additions to C++20 that made this possible (note that your code doesn't compile in earlier standard versions).

  1. Compiler will attempt to replace a != b with !(a == b) if there is no suitable != for these arguments.

  2. If there is no suitable a == b, compiler will attempt b == a as well.

So, what happens - compiler first notices that it doesn't know how to compare 10 != i, so it tries !(10 == i). There is still no suitable comparison, so it tries !(i == 10) and it can finally be done using your implicit constructor to convert 10 to Integer.

It can be easily verified by adding more info to debug print:

  bool
operator== (const Integer &i) const
{
std::cout << x << "==" << i.x << ' ';
return x == i.x;
}

will print 0==10 1 in the last line (see it online).


As noticed in comments, you don't even need operator !=, due to aforementioned behaviour C++20 compiler will automatically convert any such call to operator ==.



Related Topics



Leave a reply



Submit