Reputation: 35485
I heard C++ enables overriding both operator==
and operator!=
because in certain cases a != b
can be implemented to be more efficient than !(a == b)
.
I've thought about this and can't imagine a case where this is true.
What are some examples where it makes sense, performance-wise or other, to have separate implementations for operator==
and operator!=
?
Upvotes: 3
Views: 402
Reputation: 16204
If you want to have the rule that a == b
returns true exactly when a != b
returns false, then indeed, there is little reason to have two implementations, unless you are hoping to optimize out a single !
somehow. (Which would very rarely make a difference, and is better done by the optimizer.)
However, C++ does not generally assume that the operator overloads obey rules like this.
For instance, you might think also that, you should only need to overload operator <
, and then get operator >
, operator <=
, operator >=
and operator ==
for free. Since all these can be defined in terms of operator <
if you assume that it returns a bool and the relation is supposed to be a partial order.
But, operators are also used to give more complex syntax and semantics in some cases. If these kinds of "identities" were imposed it would make some things like expression templates impossible, for instance.
C++ doesn't impose any "identities" on you. You can give the operators whatever meaning you feel like, for better or worse.
So, I think what you heard may be a misconception. The reason you have this freedom is not to give more opportunities for "efficiency", it's to allow you to give the operators the meaning that you want when they are used with your custom classes.
For completeness, here's an example of what I'm talking about.
namespace expression_builder {
struct arg {
bool operator()(bool input) const {
return input;
}
};
template <typename E>
struct negate {
E e;
bool operator()(bool input) const {
return !e(input);
}
};
template <typename E1, typename E2>
struct equals {
E1 e1;
E2 e2;
bool operator()(bool input) const {
return e1(input) == e2(input);
}
};
template <typename E1, typename E2>
struct not_equals {
E1 e1;
E2 e2;
bool operator()(bool input) const {
return e1(input) != e2(input);
}
};
// Operator overloads
template <typename T>
auto operator!(T t) -> negate<T> {
return {t};
}
template <typename T1, T2>
auto operator==(T1 t1, T2 t2) -> equals<T1, T2> {
return {t1, t2};
}
template <typename T1, T2>
auto operator!=(T1 t1, T2 t2) -> not_equals<T1, T2> {
return {t1, t2};
}
} // end namespace expression_builder
int main() {
using expression_builder::arg;
auto my_functor = (arg == (arg != (!arg)));
bool test1 = my_functor(true);
bool test2 = my_functor(false);
}
In this code, operator overloading is being used to allow you construct function objects to implement simple boolean functions. The function construction process happens entirely at compile time, so the resulting code is very efficient. People use this with much more complex examples to do certain kinds of functional programming very efficiently in C++. And it's crucial that operator ==
has a very different implementation from operator !=
here.
Upvotes: 3
Reputation: 3396
In very simple situations, the "implement equality in terms of inequality" (or vice versa) idiom will suffice. On x86, the cmp
instruction is used for both equality and inequality. Take the following example:
struct Foo
{
bool operator==(const Foo& rhs)
{
return val == rhs.val;
}
bool operator!=(const Foo& rhs)
{
return val != rhs.val;
}
int val;
};
Foo a{20};
Foo b{40};
Foo c{20};
int main()
{
(void)(a == b);
(void)(a == c);
(void)(a != b);
(void)(a != c);
}
This is going to compile to identical assembly, sans sete
versus setne
. People may split hairs and make vague assertions about branch prediction, pipelines, CPU cache, etc. But they're really just vacuous statements.
In complex situations, it may be tempting to give different semantics for operator==
and operator!=
, but I don't agree with this principle:
You're violating the user's expectation that these two operators are the inverse of each other. For example is there any difference between !(a == b)
or a != b
? You easily fall into the trap of other languages like PHP and Javascript where equality is a special type of Hell.
Hiding complex objects behind operator overloading and iterators can hurt performance badly. People use these features on the assumption that they're cheap (and in most cases they are.) An expensive "iterator" or "equality" makes it very difficult to use properly.
"When does it makes sense" is more of a question answered by business requirements than proper design, unfortunately.
Upvotes: 1
Reputation: 57774
The first example which comes to mind is implementations analogous to the NULL value for SQL. In that, comparing two objects—either of which are NULL—does not mean they are equal. Only if both are not NULL does it makes sense to return equality.
Upvotes: 4