C++20 std::vector comparison weird behaviour
Asked Answered
A

1

18

Overloading the operator bool() for a custom class T breaks std::vector<T> comparison operators.

The following code tried on the first online compiler google suggest me prints

v1 > v2: 0
v1 < v2: 1

when operator bool() is commented and

v1 > v2: 0
v1 < v2: 0

when it's uncommented.

#include <iostream>
#include <vector>

class T {
    int _value;
public:
    constexpr T(int value) : _value(value) {}
    constexpr bool operator==(const T rhs) const { return _value == rhs._value; }
    constexpr bool operator!=(const T rhs) const { return _value != rhs._value; }
    constexpr bool operator <(const T rhs) const { return _value  < rhs._value; }
    constexpr bool operator >(const T rhs) const { return _value  > rhs._value; }
    
    //constexpr operator bool() const { return _value; } // <-- breaks comparison
};

int main()
{
    auto v1 = std::vector<T>{1,2,3};
    auto v2 = std::vector<T>{1,2,9};
    std::cout << "v1 > v2: " << (v1 > v2) << std::endl;
    std::cout << "v1 < v2: " << (v1 < v2) << std::endl;
    return 0;
}

This appears to be true only starting from C++20. What's changed underneath in std::vector?

Avid answered 18/4 at 10:38 Comment(9)
The comparison uses synth-three-way since C++20, which prefers <=> over <.Terminus
Aside: implicit conversions are not your friends.Bifocals
Still, he is providing an operator<, so what's the explanation-chain that makes it work or not work, depending on whether operator bool is there or not?Wauters
@ChristianStieber: HolyBlackCat's answer already addresses that.Carola
Side note, in your case it would be enough to just constexpr auto operator<=>(const T& rhs) const = default;. No need to define each operator by hand.Severance
As a side-note, you're violating the general rules for comparison operator overloading (they should be non-member functions, on top of <=> being the better C++20 solution, and friend auto operator<=>(const T&, const T&) = default; would be enough to have the compiler implement it for you in this case).Carola
@Carola thanks. I still don't get why a synthesized <=> uses an implicit bool conversion, or why the C++ standard decided it's a good idea to prefer a synthesized <=> over a provided < -- but for now, I guess having read about this at some point should suffice. I rarely use conversion operators anyway, and never an implicit bool, so I still feel reasonably safe :-)Wauters
Side note (unrelated to the problem): you can =default on operator== and operator!= in C++20, and for operator<=> of course.Forbis
@ChristianStieber Think about what x <=> y can mean here. There's no candidate for x.operator<=>(y). But there is one for operator<=>(x, y) - which is operator<=>((bool)x, (bool)y). There's no way to differentiate in the language between "operator<=> works because you provided it" and "operator<=> works because you inherited it from an implicit conversion but you really had meant to use operator<".Fie
I
27

C++20 replaces the individual <,<=,>,>= operators of std::vector (and of many other standard classes) with a single <=>.

Internally it tries to use <=> to compare the elements, and falls back to the old operators if the type doesn't overload <=>.

Since you have a non-explicit operator bool, applying <=> converts both operands to bool and compares those. The fix is to make operator bool explicit (which is a good idea in general) (so that <=> fails and vector falls back to the old operators), and/or replace <,<=,>,>= with <=> (which is also a good idea in general).

Ionia answered 18/4 at 10:49 Comment(1)
And to be clear, you can still use your type in conditional tests even when operator bool is explicit, so this won't mean you need to perform explicit conversions in the 99% of cases where boolean conversion is desired, while still avoiding implicit conversions in the 99% of cases where you don't want it.Carola

© 2022 - 2024 — McMap. All rights reserved.