Search code examples
c++c++20comparison-operators

Why does pair's comparison operator prefers conversion over user-provided operator<?


After switching to C++20 I found that some of our tests failed. The output of the following code is different between C++17 and C++20 modes:

https://godbolt.org/z/hx4a98T13

class MyString
{
public:
    operator char const *() const
    {
        std::printf("convert ");
        return nullptr;
    }

    bool operator<(MyString const & other) const
    {
        std::printf("MyString::operator< ");
        return 1;
    }
};

int main()
{
    MyString s1;
    MyString s2;
    std::printf("s1 < s2 = %d\n", s1 < s2);

    std::pair<MyString, MyString> pair1;
    std::pair<MyString, MyString> pair2;
    std::printf("pair1 < pair2 = %d\n", pair1 < pair2);
}
/// C++17
MyString::operator< s1 < s2 = 1
MyString::operator< pair1 < pair2 = 1
/// C++20
MyString::operator< s1 < s2 = 1
convert convert convert convert pair1 < pair2 = 0

It seems that std::pair::operator<=> prefers synthesized operator< over user-provided. Why is that so?

PS: I do know that making conversion operator explicit solves the issue, but I would really like to know what causes this behavior.


Solution

  • This is a (presumably accidental) breaking change in C++20.

    In C++20, ordering comparisons on std::pair use operator<=>, which in turn will use operator<=> (if possible) on its components.

    The only available operator<=> on your MyString type is on the converted char const *, so that becomes the chosen comparison.


    You can fix this by having MyString implement operator<=> instead of operator<.

    Or you can make operator char const *() explicit, which will provide no operator<=>, causing the the three-way comparison to be synthesized from your operator<.