1

Is it possible to merge some operators of class that have same logic but different operators to not copypasting. For example, I have class with operators +=, -=:

class Matrix {
public:
    Functor& operator+=(const Functor& rhs) {
        for (int i = 0; i < num; ++i) {
            v[i][j] += rhs.v[i][j];
        }
        return *this;
    }

    Functor& operator-=(const Functor& rhs) {
        for (int i = 0; i < num; ++i) {
            v[i][j] -= rhs.v[i][j];
        }
        return *this;
    }

private:
    int rows_ {};
    int columns_ {};
    std::vector<std::vector<double>> matrix_;
};

Does C++ has something like:

Functor& operator(+=, -=)(const Functor& rhs) {
        for (int i = 0; i < num; ++i) {
            v[i][j] (+=, -=) rhs.v[i][j];
        }
        return *this;
    }
notamaster
  • 53
  • 4
  • No. Nothing like that in C++. – user4581301 Jun 30 '22 at 20:49
  • You can do it by passing a lambda to a common implementation (solution to follow). – lorro Jun 30 '22 at 20:49
  • 1
    If you have the unary -, you can do call +=(-rhs) from operator-=. – 273K Jun 30 '22 at 20:51
  • There are [some good tricks covered here](https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading) to base operations on other operations that do almost the same thing. For example, you can build `+` on top of `+=` – user4581301 Jun 30 '22 at 20:52
  • @user4581301 see solution below. – lorro Jun 30 '22 at 20:54
  • Already figured out what you were going to do and updated comment – user4581301 Jun 30 '22 at 20:55
  • @notamaster Also, the code is buggy (but understandable) in this form: you didn't specify `j` and `n`. Suggest including these (i.e., nested loops). – lorro Jun 30 '22 at 21:26
  • Whatever complex solution you come up with for this is going to be worse than the original code, from a readability, maintenance and performance perspective – M.M Jun 30 '22 at 21:34
  • Operators on matrixes only make sense when the dimensions match. You should make the dimensions template parameters so the type system can verify you are not trying to add matrixes of different sizes. You can then also use `std::array` instead of `std::vector`, which allows aggregate initialization. – Goswin von Brederlow Jun 30 '22 at 21:59

1 Answers1

0

You can do it by passing a lambda to a common implementation:

class Matrix {
private:
    template<typename Op>
    Functor& opImpl(const Functor& rhs, Op op) {
        for (int i = 0; i < rows_; ++i) {
          for (int j = 0; j < columns_; ++j) {
            Op(v[i][j], rhs.v[i][j]);
          }
        }
        return *this;
    }

public:
    Functor& operator+=(const Functor& rhs) {
        return opImpl(rhs, [&](double& l, const double r) { r += l; });
    }

    Functor& operator-=(const Functor& rhs) {
        return opImpl(rhs, [&](double& l, const double r) { r -= l; });
    }

private:
    int rows_ {};
    int columns_ {};
    std::vector<std::vector<double>> matrix_;
};

Normally, you'd first implement operator+ and operator- and implement operator+= and operator-= in terms of the former. In that case, you can use std::plus<double>() and std::minus<double>() for the lambda if you're in c++14 or later (thx @StoryTeller).

You can further save space by either having the operators in a base class, having it as using from a namespace or simply by a macro:

#define IMPLEMENT_OP(RET, OP, ARG, T) \
   RET operator ## OP ## (ARG rhs) { \
      return opImpl(rhs, T::operator ## op); \
   }

Another way to solve this - still by delegating - is to use constexpr if:

class Matrix {
private:
    template<char op1, char op2 = ' '>
    Functor& opImpl(const Functor& rhs) {
        for (int i = 0; i < rows_; ++i) {
          for (int j = 0; j < columns_; ++j) {
            if constexpr (op1 == '+' && op2 == '=') {
                v[i][j] += rhs.v[i][j];
            }
            if constexpr (op1 == '-' && op2 == '-') {
                v[i][j] -= rhs.v[i][j];
            }
            // TODO: further ops, error handling here
          }
        }
        return *this;
    }

public:
    Functor& operator+=(const Functor& rhs) {
        return opImpl<'+', '='>(rhs);
    }

    Functor& operator-=(const Functor& rhs) {
        return opImpl<'-', '='>(rhs);
    }

private:
    int rows_ {};
    int columns_ {};
    std::vector<std::vector<double>> matrix_;
};
Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
lorro
  • 10,687
  • 23
  • 36
  • What's `double::operator+=`? – StoryTeller - Unslander Monica Jun 30 '22 at 20:54
  • @StoryTeller-UnslanderMonica It's the function that gets called when you do `double d = 0.0, e = 1.0; d += e;`. It takes two arguments, modifies the left by adding the right to it. Being double is based on the type in `matrix_`, but you can do this for arbitrary types. – lorro Jun 30 '22 at 20:57
  • You've got the right idea, but I'm not sure this will save any code when carried through to completion. You're going to have to write `double::operator-=` and `double::operator+=`. – user4581301 Jun 30 '22 at 20:58
  • @user4581301 you can put those to a CRTP base class or have a macro for those (going to implement it here). – lorro Jun 30 '22 at 20:59
  • 3
    @lorro Hint: [std::plus](https://en.cppreference.com/w/cpp/utility/functional/plus), [std::minus](https://en.cppreference.com/w/cpp/utility/functional/minus) – PaulMcKenzie Jun 30 '22 at 20:59
  • Now we're rocking. I often overlook c++14. – user4581301 Jun 30 '22 at 21:00
  • @lorro - You misunderstood, it's a leading question. There is no such function to be named in C++. Fundamental types aren't rigged like classes. `double::whatever` is ill-formed. If you want to present exposition only code that's fine, but it should be clearly marked. – StoryTeller - Unslander Monica Jun 30 '22 at 21:05
  • @StoryTeller-UnslanderMonica fixing with lambda then. – lorro Jun 30 '22 at 21:06
  • And that's where I was headed. As soon as you write those lambdas, you've now written more code than you would have writing out the two operators in full. – user4581301 Jun 30 '22 at 21:08
  • @user4581301 Idea here is _how_ to do it, hot how to optimally do it. Optimally, you'd have `+`, unary `-` implemented first; then binary `-`, then `+=` and `-=` in terms of these. If you do it that way, it'll be way simpler. You might have much of these as mixins. – lorro Jun 30 '22 at 21:10
  • @user4581301 Provided an alternative, much shorter delegation via if constexpr in implementation. Also note, the actual (working) implementation of OP's operators is likely longer and should contain nested loops I think. – lorro Jun 30 '22 at 21:33
  • You can make a base class that has virtual `operator+`, unary `operator-` and `operator<=>` and defines all the rest from that. Then you only have to implement those three. Makes me wonder if the compiler would optimize away all the overhead compared to implementing each operator separate. – Goswin von Brederlow Jun 30 '22 at 22:04
  • @GoswinvonBrederlow `boost::operators<>` does just that, except that it's CRTP so you won't have an overhead of the virtual call. – lorro Jun 30 '22 at 22:07
  • @lorro That works too. But doesn't the virtual call get eliminated by the compiler because it inlines the `opImpl`? – Goswin von Brederlow Jun 30 '22 at 22:11
  • @GoswinvonBrederlow if it's `final`, it can usually inline it (`operator+` and the others you mean I think), if it's not, it's not sure. Btw. Bjarne has a (heavily overlooked) paper on how to fix that virtual overhead for compiler implementors. – lorro Jun 30 '22 at 22:14
  • @GoswinvonBrederlow The virtual call might be eliminated, but there is still other overhead in needing to create a vtable and storing a pointer in the object (increasing the size for no good reason) – Artyer Jul 01 '22 at 03:32