Consider the following piece of code:
struct B {
friend bool operator< (const B&, const B&);
friend bool operator==(const B&, const B&);
};
struct D : B {
friend std::strong_ordering operator<=>(const D&, const D&) = default;
};
bool less(const D& a, const D& b) {
return a < b;
}
Operators <
and ==
are intentionally left undefined to avoid optimizations related to their inlining.
When I look at the assembly code (gcc) for the less()
function, it appears to be fully equivalent to the following (this is true for both std::strong_ordering
and std::weak_ordering
return types of operator<=>
):
bool less(const D& a, const D& b) {
if (static_cast<const B&>(a) == static_cast<const B&>(b))
return false;
else
return static_cast<const B&>(a) < static_cast<const B&>(b);
}
whereas I was expecting to see this:
bool less(const D& a, const D& b) {
return static_cast<const B&>(a) < static_cast<const B&>(b);
}
Why do compilers generate an additional call to operator==(const B&, const B&)
instead of just calling operator<(const B&, const B&)
? Is it a missing optimization or there is a fundamental reason to check equality?
This is how synthesis of tree way commemorator is defined by standard.
11.11.3 Three-way comparison
The synthesized three-way comparison of type R ([cmp.categories]) of glvalues a and b of the same type is defined as follows:
If a <=> b is usable ([class.compare.default]), static_cast<R>(a <=> b).
Otherwise, if overload resolution for a <=> b is performed and finds at least one viable candidate, the synthesized three-way comparison is not defined.
Otherwise, if R is not a comparison category type, or either the expression a == b or the expression a < b is not usable, the synthesized three-way comparison is not defined.
Otherwise, if R is strong_ordering, then
a == b ? strong_ordering::equal : a < b ? strong_ordering::less : strong_ordering::greater
Basically since in your example compiler do not see implementation of bool operator< (const B&, const B&);
and bool operator==(const B&, const B&);
it must assume that they can have some side effects and must follow above definition literally.
Question is now can compiler optimize this if it could see through bool operator< (const B&, const B&);
and bool operator==(const B&, const B&);
and prove they do not have any side effects?
Apparently it can https://godbolt.org/z/qzzjsxeYT but in such case also less2
is also optimized in same way. Note that after that assembly of operator<(B const&, B const&)
is same as assemblies of less
and less2
, so operator<
for B
and D
are equivalent.