https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114153

            Bug ID: 114153
           Summary: std::less prefers operator const void*() over
                    operator<=>() in C++20 mode
           Product: gcc
           Version: 12.3.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: marc.mutz at hotmail dot com
  Target Milestone: ---

std::less (and other related types like std::greater_equal, etc) is implemented
in the following way:
* if `operator<(T, U)` is defined for the argument types, it is called.
* otherwise, if the argument types are convertible to `const volatile void *`,
such conversion is performed, and it boils down to comparing the pointers.

Now, assume a type which has an `operator const void *() const`, and provides
`operator==()` and `operator<=>()` to generate all relational operators, the
same way the std types do.

So std::less will not use `operator<=>()`, but cast to `const void *` and
compare pointers. 
This is wrong, because `operator<=>()` implies all relational operators, so it
can be used to do the proper comparison. libc++ gets this right:

// https://godbolt.org/z/E55eeosP9
// Courtesy of Ivan Solovev <ivan.solo...@qt.io>
#include <compare>
#include <functional>
#include <iostream>

struct S
{
    int val;

    S(int v) : val(v) {}

    operator const void *() const { std::cout << "cast\n"; return &val; }

    friend bool operator==(S lhs, S rhs) noexcept 
    { std::cout << "op==\n"; return lhs.val == rhs.val; }
    friend std::strong_ordering operator<=>(S lhs, S rhs) noexcept 
    { std::cout << "op<=>\n"; return lhs.val <=> rhs.val; }  
};

int main()
{
    const S arr[] = {S{2}, S{1}};
    // In C++20 mode it compares pointers, and so considers that arr[1] >
arr[0],
    // which is wrong!
    return std::greater_equal<>{}(arr[0], arr[1]) ? 0 : 1;
}

Reply via email to