2

I am trying to write a

friend T operator+( lhs, rhs){
};

Now, I would like to avoid construction of temporaries when possible.

For example:

  1. If both lhs and rhs are const T& the operator+ should create a temp copy-constructing from lhs, then temp += rhs; to it and finally return std::move(temp).
  2. If lhs is T&& then I want to directly sum rhs to it as in lhs += rhs; and then return std::move(lhs). This case avoid a copy-construction in (A+B)+C since the output of (A+B) is not needed outside the expression. Notice that we might have to do temp(std::move(lhs)) to have a common code for both Case 1. and Case 2.
  3. Similarly for the other two case when either rhs or both lhs and rhs are T&&.

By writing four overloads I have managed to do this. Now, I have read that it is possible to take advantage of templates and forward to reduce the number of overloads or maybe even write it in just one template function. I am having trouble understanding how.

It seems to be that I need

template<typename R, typename S, typename T>
R operator+(S&& lhs, T&& rhs){
   // ...
};

But what I have tried for the content doesn't work.

There is an additional issue that I might have to deal with. If instead of operator+ we need operator- or operator/ the body of the method it not necessarily similar in all the cases. In A/B, if A is T&& we can A /= B. But if B is the one that is T&& then we might need to do B.reciprocal() *= A. So, I think I also need a way to know which case was entered in the template.

Note: My multiplication IS commutative (component-wise multiplication in matrices).

Could you give some comments and ideas on how to approach this problem?

Note to self and to the next reader: A link to some reading on the use of expression templates.

Kae
  • 279
  • 2
  • 10
  • 6
    I think for true lazy-evaluation you're going to have to use expression templates. – Rapptz Oct 27 '14 at 00:37
  • 1
    I'm not sure if this level of generality is warranted. Move semantics is about heavy, resource-owning objects. Division and subtraction are about numbers. – Kerrek SB Oct 27 '14 at 00:37
  • @KerrekSB Use some abstraction. I never said that `A,B,C` were numbers. In my case they are actually matrices, and they will be big matrices. – Kae Oct 27 '14 at 00:38
  • @Rapptz Maybe you are saying something that is going to be useful to me, but before I jump to read about expression templates let me understand what you mean by *true lazy-evaluation*. My goal is *only* to avoid the copy-constructions that can be avoided. I am not sure how *true* is this level of laziness. For this goal, do you think I will need to learn about expression templates? – Kae Oct 27 '14 at 00:43
  • 1
    If your operators are commutative, one could try to apply a technique for boilerplate reduction by reordering the arguments such that the first is always an rvalue: http://coliru.stacked-crooked.com/a/14d811e4d7e9297b (but this increases the amount of moves necessary, since the rvalue is potentially created within one function and therefore has to be moved out). – dyp Oct 27 '14 at 00:48
  • @dyp This can help reduce my 4+4=8 overloads for `operator+` and `operator*` (my operator* is component-wise and therefore commutative) down to 6 methods. It is some reduction at least. – Kae Oct 27 '14 at 00:57
  • 1
    Couple notes: `return temp;` will move, you don't need `return std::move(temp);` (unless the return variable is an lvalue reference, and then you probably shouldn't be trying to move it). You've already noted that commutativity doesn't apply to all operators... for matrices it doesn't even apply to `*`. Matrix multiplication and division results may not be the same size as either operand, so reusing the operand isn't possible. Even for square matrices, `operator*=` is implemented in terms of `*` then `=`, because the left-hand side is needed repeatedly during the computation. – Ben Voigt Oct 27 '14 at 00:57
  • Oh, you're doing arithmetic on a valarray-like type, not a matrix. Adjust above advice appropriately. – Ben Voigt Oct 27 '14 at 00:58
  • @Karene Do you count `recycle_any_and_apply` as a "method" here, or are there multiple types involved? – dyp Oct 27 '14 at 01:00
  • @dyp Yes, I was counting them. Shouldn't I? 4 `recycle_any_and_apply`, 1 `operator+` and 1 `operator*`. – Kae Oct 27 '14 at 01:02
  • @Karene: when dealing with operations on large matrices, it pays off to not traverse the matrices multiple times as this destroys cache locality. The approach Rapptz is referring to essentially builds up an expression tree from the actual expression which is evaluated upon assigning the result. In the simplest case of a sequence of additions, each array element is just read once and the result is just written once plus holding one value on the stack. With your "lazy" evaluation a temporary results is written once per operation. – Dietmar Kühl Oct 27 '14 at 01:03
  • @Karene I just wasn't sure what you're counting ;) To me, `recycle_and_apply` is some kind of library function (or rather, a sketch of a library function), since it can be reused for (m)any type(s) and (m)any commutative operator(s). – dyp Oct 27 '14 at 01:07
  • It'd be really nice if you post a test suite: show a list of expressions using this operator `+` for which you want to minimize copies – M.M Oct 27 '14 at 03:09

1 Answers1

2

Write a function named sum. sum has 3 overloads: lhs&&, rhs const& and lhs const&, rhs&&, ... -- the ... is important, as it makes it the worst match, removing ambiguity. And finally const& lhs, const& rhs.

Then template<lhs, rhs> auto operator+(lhs&&,rhs&&)->decltype perfect forwards to sum.

Once you get that working we can move on. We can either reimplement it for each operator, or move up a level of abstraction.

You have a mutating binary op (+= etc) and an anti-symmetric transformation (noop for most, inverse for divide -- the binary op for divide is *= btw). Pass those to a dispatcher overloaded like sum.

Use function objects for those two, and rewriting the sum functions to take increase_mat{} and noop{} should be easy.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524