Search code examples
c++operatorsc++17comparison-operators

Is there any better way to define relational operators in C++ pre C++20?


It gets pretty tedious for me to write code like

class date {
private: 
    int day, year, month;
    int comp(const date & Other) const;
public:
    date(int D, int Y, int M) : day (D), year (Y), month (M)  {}

    friend bool operator==(const date & L, const date & R) { return L.comp(R) == 0 ; }
    friend bool operator!=(const date & L, const date & R) { return L.comp(R) != 0 ; }
    friend bool operator> (const date & L, const date & R) { return L.comp(R) > 0 ; }
    friend bool operator>=(const date & L, const date & R) { return L.comp(R) >= 0 ; }
    friend bool operator< (const date & L, const date & R) { return L.comp(R) < 0 ; }
    friend bool operator<=(const date & L, const date & R) { return L.comp(R) <= 0 ; }
}

All that relational operators stuff is boilerplate (same for every single class that needs them). There must surely be a better way to do all this. I know that C++ 20 offers the spaceship operator, but I don't have that. So my question is this: How do I avoid this boilerplate code using older than C++ 20 features?


Solution

  • You could use Boost.Operators (or implement such a thing yourself), but you'd still have to write two functions:

    class date : public totally_ordered<date> {
    private: 
        int day, year, month;
        int comp(const date & Other) ;
    public:
        date(int D, int Y, int M) : day (D), year (Y), month (M)  {}
    
        friend bool operator==(const date & L, const date & R) { return L.comp(R) == 0 ; }
        friend bool operator< (const date & L, const date & R) { return L.comp(R) < 0 ; }
    }
    

    Note that you generally don't want to use the pattern of defining == in terms of a three-way ordering since == can be much more efficient in general (e.g. consider the case of strings where you know differently-sized strings are unequal right away, but you don't know what the result of compare() would be without actually comparing an arbitrarily large number of characters).

    Note also that a general problem with this approach is deciding whether x <= y is defined as !(y < x) or x < y || x == y. Most library solutions choose the former (fewer comparisons), though this gives the wrong answer on partial orders - which was part of the motivation for <=> to begin with. Boost.Operators addresses this by also having a partially_ordered<T> which correctly defines <= in terms of both operations.


    There must surely be a better way to do all this.

    As Nicol says, that's why we added <=>:

    class date {
    private: 
        int year, month, day; // NB: I reordered these
    public:
        friend auto operator<=>(const date&, const date&) = default;
    };