Potential performance pessimization for aggregate/container comparators
is indeed there, which can be seen from the following example
#include <utility>
#include <iostream>
struct S
{
int a;
S(int a) : a(a) {}
friend bool operator <(const S& lhs, const S& rhs)
{
bool b = lhs.a < rhs.a;
std::cout << "cmp " << lhs.a << " < " << rhs.a <<
" = " << b << std::endl;
return b;
}
};
int main()
{
std::pair<int, S> p1{ 0, 1 }, p2{ 0, 2 };
bool b = p1 < p2;
std::cout << "result = " << b << std::endl;
b = p2 < p1;
std::cout << "result = " << b << std::endl;
}
This case does not suffer from the original problem (no implicit
conversion present), meaning that in this case the comparison results
are consistent between C++17 and C++20. However, the paths that lead to
those results are still different.
When compiled as C++17 it outputs
cmp 1 < 2 = 1
result = 1
cmp 2 < 1 = 0
result = 0
When compiled as C++20 it outputs
cmp 1 < 2 = 1
result = 1
cmp 2 < 1 = 0
cmp 1 < 2 = 1
result = 0
C++20 still has to route the `std::pair` comparison through the `<=>`
operator. And ultimately it has to implement that operator based on the
available user-provided `<` comparison for `S`. In general case it takes
more calls to `<` to properly calculate the three-state result for
`<=>`, as evidenced by the above output. E.g. when "less" comparator
returns `false` an extra call is required to tell "equivalent" from
"greater".
If comparisons for `S` are "heavy", this might easily result in
significant pessimization.
So, even though the ultimate goal of three-state comparison support in
C++20 is to improve the performance of heavy comparisons, it does not
come for free: it still requires the user to take active steps to
provide efficient `<=>` comparators for their classes. Without it the
code might work correctly, but sub-optimally.