2

What I want to have: R = V + R = R + V. How I do it:

R operator+(const R &r, const V &v) { return R(r) += v; }
R operator+(R &&r, const V &v) { return R(r) += v; }
R operator+(const R &r, V &&v) { return R(r) += v; }
R operator+(R &&r, V &&v) { return R(r) += v; }

R operator+(const V &v, const R &r) { return r + v; }
R operator+(V &&v, const R &r) { return r + v; }
R operator+(const V &v, R &&r) { return r + v; }
R operator+(V &&v, R &&r) { return r + v; }

Is there a shorter way?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Jecke
  • 239
  • 2
  • 13
  • related: http://stackoverflow.com/questions/13166079/move-semantics-and-pass-by-rvalue-reference-in-overloaded-arithmetic and https://stackoverflow.com/questions/2696156/how-to-reduce-redundant-code-when-adding-new-c0x-rvalue-reference-operator-ove – NathanOliver Nov 15 '16 at 15:39
  • to clarify, is `V` such that doing `r + v` is faster if `v` can be moved out of? – M.M Nov 15 '16 at 21:14
  • I assume that not-copying v or r is faster than copying it. (So if I don't have to copy it, I shouldn't). Here, I also assumed that I have copy and move constructors, so when I write R(r), it either copies it or moves it, depending on whether r is an rvalue - at least that was my intent. – Jecke Nov 16 '16 at 10:06

2 Answers2

2

Because you will create an instance of r from either const R& or R&& anyway, this will shorten by 4 lines:

R operator+(R r, const V& v) { return std::move(r += v); }
R operator+(R r, V&& v) { return std::move(r += std::move(v)); }

R operator+(const V& v, R r) { return std::move(r += v); }
R operator+(V&& v, R r) { return std::move(r += std::move(v)); }

However, I think this is enough for you in all cases unless your R::operator+=(V&&) have difference implementation from R::operator+=(const V&):

R operator+(R r, const V& v) { return std::move(r += v); }

R operator+(const V& v, R r) { return std::move(r += v); }

My answer was written when I didn't aware of this implementation:

R operator+=(R&& r, const V& v)
R operator+=(R&& r, V&& v)

The answer of Yakk is better when take those functions into account.

Danh
  • 5,916
  • 7
  • 30
  • 45
  • 1
    Unless `+=` can take a `V` by `&&`. Also note you should `move` into the `+=` above for `r`, otherwise `+=` has no way to know it can reuse the storage or return an rvalue. – Yakk - Adam Nevraumont Nov 15 '16 at 15:52
  • @Yakk I added that note. – Danh Nov 15 '16 at 15:54
  • How do you know that I will always create a copy of `r`? If I had `R r = some_function_returning_R() + v`, then wouldn't the `some_function_returning_R()` be an rvalue, and then, couldn't I add `v` to it and then return it as a result of `operator+(R &&r, const V &v)` without having to create any copy of `r`? – Jecke Nov 15 '16 at 16:18
  • @Jecke My wording is not quite good. But at the end of the day, you will always create an instance of `R` from either `const R&` or `R&&`. – Danh Nov 15 '16 at 16:21
  • @Danh - but why? If I get an R&& in an argument, wouldn't I be able to reuse it and not have to create another instance of R? Something like this: `R operator+(R &&r, const V &v) { return std::move(r += v); }? – Jecke Nov 15 '16 at 16:22
  • @Jecke And you will end up moving it, which in turn call its move constructor, my implementation call move constructor first, then add `v` to it and return it. – Danh Nov 15 '16 at 16:26
  • @Dahn sorry for a noob question, but where does your implementation call move constructor for R? – Jecke Nov 15 '16 at 16:34
  • @Jecke when you call `std::move(r)+v`, the argument is `R&&`, that function is passed-by-value, hence a move constructor is called overthere. Note: in some case, it's optimized out as copy elision – Danh Nov 15 '16 at 16:42
  • I don't understand. I asked why don't you define a function `R operator+(R &&r, const V &v)` and `R operator+(R &&r, V &&v)`. You said that your implementation calls move constructor for R, but I don't see any place where it does. You mentioned `std::move(r)+v`, but there is no such instruction in your code. – Jecke Nov 15 '16 at 16:57
  • I hope [this example](http://coliru.stacked-crooked.com/a/0bebcaa67c65a5b8) will help you understand it. – Danh Nov 15 '16 at 17:03
  • The `main` function in line 41 want to call `operator+` for `R&&` and `V`. It found `R operator+(R r, const V& v)` then it need an implicit conversion from `R&&` to `R`, which ended up by calling move constructor of R – Danh Nov 15 '16 at 17:06
  • @Dahn I see, thanks :) But still, the R(const &R) constructor (line 10) is called at the end. If I understand correctly, that's because the return statement creates a copy? Is it possible to avoid it? – Jecke Nov 15 '16 at 17:58
  • @Danh I've changed `R operator+(R r, const V& v) { return r += v; }` to `R operator+(R r, const V& v) { return std::move(r += v); }` and now it says "13 26 13" instead of "13 26 10", so, if I understand correctly, `r` is moved twice, instead of being copied. That's better, right? – Jecke Nov 15 '16 at 18:05
  • @Jecke Yes, that's better if move is cheap (it's correct is most case) – Danh Nov 16 '16 at 01:37
2

Assuming:

R& operator+=( R& r, const V& v );
R& operator+=( R& r, V&& v );
R operator+=( R&& r, const V& v ) { r += v; return std::move(r); }
R operator+=( R&& r, V&& v ) { r += std::move(v); return std::move(r); }

we should have:

R operator+( R r, V const& v ) { return std::move(r)+=v; } 
R operator+( R r, V && v ) { return std::move(r)+=std::move(v); } 
R operator+( V const& v, R r ) { return std::move(r)+=v; } 
R operator+( V && v, R r ) { return std::move(r)+=std::move(v); } 

where I assume R is cheap-to-move, while += with a V&& is only marginally more efficient than a V const&.

Note that the return value of R operator+=( R&& r, ? ) should be a value. I implement it in terms of +=(const&,?), then just move into the return value.

This means you have to implement two += operators beyond the above boilerplate.

If there is no gain from a moved-from V we get:

R& operator+=( R& r, const V& v );
R operator+=( R&& r, const V& v ) { r += v; return std::move(r); }
R operator+( R r, V const& v ) { return std::move(r)+=v; } 
R operator+( V const& v, R r ) { return std::move(r)+=v; } 

3 boilerplate, one actually implemented function.

If you dislike R operator+=( R&& r, const V& v) we can rewrite this as:

R& operator+=( R& r, const V& v );
R operator+( R r, V const& v ) { return std::move(r+=v); } 
R operator+( V const& v, R r ) { return std::move(r+=v); } 

and similarly for the V&& cases if needed:

R& operator+=( R& r, V&& v );
R operator+( R r, V&& v ) { return std::move(r+=std::move(v)); } 
R operator+( V&& v, R r ) { return std::move(r+=std::move(v)); } 

We make a copy of the R in the signature of operator+ rather than internally; if we are going to copy it anyhow, may as well do it in the signature.

Want speed? Pass by value. is the technique used to remove the R const& and R&& overloads as redundant.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Hmm, 1. is `return std::move(r)+=v;` different from `return std::move(r+=v)`? If so, how? 2. Shouldn't I consider a possibility of `R operator+(R &&r, ?)`? – Jecke Nov 15 '16 at 16:30
  • I do not see the reason to implement `R operator+=( R&& r, const V& v )` or `R operator+=( R&& r, V&& v )`. Operator `+=` implies, that you modify the left operand. Otherwise you would use `+`. Why should you use `+=` to overwrite the left operand if you remove the content from it afterwards? – JojOatXGME Nov 15 '16 at 16:36
  • Isn't it ended up calling `R& operator+=( R& r, const V& v );` in `R operator+( R r, V const& v ) { return std::move(r)+=v; } `? Is there any benefit? – Danh Nov 15 '16 at 16:45
  • @Danh The state is moved from `+` into `+=(&&)`, then `+=(&)` is called on the state, then the state is moved out (twice, but elision-capable) and returned. – Yakk - Adam Nevraumont Nov 15 '16 at 17:40
  • @JojOatXGME If I did not implement `+=(&&)`, I'd have to make other changes to remain as efficient. Are you saking why I don't just make those other changes? – Yakk - Adam Nevraumont Nov 15 '16 at 17:43
  • @Jecke Slightly different, in that `std::move(x)+=v` returns a value, while `std::move(x+=v)` returns an rvalue reference. Not a large difference. You can do away with `+=(&&)` if you prefer, but then `+` must use `std::move(x+=y)` in those cases. You do not need `R operator+(R&&, ?)` because `R operator+(R, ?)` works, and if we assume move is cheap it will be good enough. Within `+` we are making a copy of the `R`, so why not make it in the signature? – Yakk - Adam Nevraumont Nov 15 '16 at 17:43
  • @Yakk I thought that I could avoid making a copy of R if I defined `R operator+(R &&r, const V &v)`. I thought defining it as something like `{return std::move(r+=v);}` then it wouldn't make any copy of r. – Jecke Nov 15 '16 at 17:48
  • @Yakk I'm not sure if I understand everything. Right now, my implementation looks like this: `R operator+(R r, const V &v) { return std::move(r += v); } `, `R operator+(R r, V &&v) { return std::move(r += std::move(v)); } `, `R operator+(const V &v, R r) { return r + v; } `, `R operator+(V &&v, R r) { return r + v; }` Does that solve all possible combinations? (r + v, move(r) + v, move(r) + move(v), r + move(v), v + r, v + move(r), move(v) + move(r), move(v) + r)? – Jecke Nov 15 '16 at 18:29
  • @Jecke Always `std::move( r += v )` or `std::move( r += std::move(v) )`. – Yakk - Adam Nevraumont Nov 15 '16 at 20:52
  • You mean that I shouldn't write `return r + v`? I thought that this would call one of the first two functions (depending on whether v is an rvalue), so it will be `return std::move(...)` in the end... – Jecke Nov 16 '16 at 10:08
  • @jerke Yes, I mean that. Instrument the copies and moves, yours does needless copies. – Yakk - Adam Nevraumont Nov 16 '16 at 11:30
  • @Yakk Hmm... Here http://stackoverflow.com/questions/11817873/using-stdmove-when-returning-a-value-from-a-function-to-avoid-to-copy it says that one shouldn't use `move` if there are temporary objects created in-function (which may or may not be the case, because we don't know if `r` is an rvalue... that could be solved if we had two functions: `operator+(R &&r, ?)` and `operator+(const R &r, ?)`) – Jecke Nov 17 '16 at 00:21
  • @jecke at this point I have given you about a half dozen variously optimal solutions, and described them, and explained roughly how they differ. If you have further questions ask them in a new SO question. If you see an alternative, consider making a noisy type that prints when copied/moved, and test and watch why your alternative is or is not worse. – Yakk - Adam Nevraumont Nov 17 '16 at 00:26