c++c++20spaceship-operator

Defaulted 3-way comparison generates more code than expected


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?


Solution

  • This is how synthesis of tree way commemorator is defined by standard.

    [class.spaceship]

    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:

    • (1.1)

      If a <=> b is usable ([class.compare.default]), static_­cast<R>(a <=> b).

    • (1.2)

      Otherwise, if overload resolution for a <=> b is performed and finds at least one viable candidate, the synthesized three-way comparison is not defined.

    • (1.3)

      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.

    • (1.4)

      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.