0

After I overloaded <, ==, +, -, * ... in a class named Fraction:

class Fraction:
public:
    // constructor, operator= ... defined before
    bool operator<(const Fraction& A) const;
    bool operator==(const Fraction& A) const;
    Fraction operator+(const Fraction& A) const;
    Fraction operator-(const Fraction& A) const;
    Fraction operator*(const Fraction& A) const;
    Fraction operator/(const Fraction& A) const;

I could use these above 'basic' operators to overload >, +=, ++ and + from left...

class Fraction:
public:
    bool operator>(const Fraction& A) const {
        return A < *this;
    }
    bool operator!=(const Fraction& A) const {
        return !(*this == A);
    }
    Fraction &operator+=(const Fraction& A) const {
        return *this = *this + A;
    }
    Fraction operator++() const {
        return *this += 1;
    }
    friend Fraction &operator+(const int& a, const Fraction& A) const {
        return A + a;
    }

However, I also have classes like RealNumber, Complex... They also need overloading, but only overloading <, ==, +, -, *... differs, while overloading >, +=, ++... is similar. (just a typename difference)

So I'm curious about an elegant way to reduce the similar part, I've learnt the CRTP that possible helps, but it only overloads comparison...

Dait
  • 1
  • 1
  • 3
    There are very few things about C++ that are "elegant". This isn't one of them. – Sam Varshavchik Sep 02 '22 at 15:04
  • This doesn't address the question, but usually a class like this has operators like `+=`, `-=`, etc. defined as member functions, because they modify the thing that they're applied to, and then operators like `+`, `-`, etc. defined as non-member functions that **use** `+=` and `-=` in their implementation and return newly created objects. – Pete Becker Sep 02 '22 at 18:03

2 Answers2

1

Use a CRTP base class like boost::operators to define the boring bits. You just define the methods below, and it automatically adds all the other operators for you.

class Fraction {
    : boost::operators<Fraction>
public:
    bool operator<(const Fraction& x) const {...}
    bool operator==(const Fraction& x) const {...}
    Fraction& operator+=(const Fraction& x) {...}
    Fraction& operator-=(const Fraction& x) {...}
    Fraction& operator*=(const Fraction& x) {...}
    Fraction& operator/=(const Fraction& x) {...}
    Fraction& operator%=(const Fraction& x) {...}
    Fraction& operator|=(const Fraction& x) {...}
    Fraction& operator&=(const Fraction& x) {...}
    Fraction& operator^=(const Fraction& x) {...}
    Fraction& operator++() {...}
    Fraction& operator--() {...}
}

https://www.boost.org/doc/libs/1_41_0/libs/utility/operators.htm

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
0

If we are talking about C++20, then

/* some type traits */
namespace detail {
    template <typename T> struct is_number_helper : public std::false_type {};
    template <> struct is_number_helper<Fraction> : public std::true_type  {};
    /* do for every number class */
}

template <typename T>
inline constexpr bool is_number = detail::is_number_helper<std::remove_cv_t<T>>::value;

/* concept */
template <typename T>
concept Number = is_number<T>;

/* generic operator (for all Number types) */

template <Number N>
constexpr N operator + (const N& lhs, const N& rhs) { /*...*/ }
/* ... */

/* specialized operator */

constexpr Complex operator + (const Complex& lhs, const Complex& rhs) { /*...*/ }
/* ... */

If you don't specialize an operator, the generic one will be used, if there is a specialized operator, then that.

Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11