26

In my Fedora 34 environment (g++), std::accumulate is defined as:

template<typename ITER, typename T>
constexpr inline T accumulate(ITER first, ITER last, T init)
{
  for (; first != last; ++first)
      init = std::move(init) + *first; // why move ?

  return init;
}

If the expression init + *first is already an rvalue, what is the purpose of std::move ?

curiousguy12
  • 1,741
  • 1
  • 10
  • 15

4 Answers4

25

std::move(init) + *first can sometimes generate more efficient code than init + *first, because it allows init to be overwritten. However, since (as you observed) the result of the + will generally be an rvalue, there is no need to wrap the entire expression in a second std::move.

For example, if you are accumulating std::strings, then std::move(init) + *first might be able to append *first into reserved-but-not-yet-used space in init's buffer instead of having to allocate a new buffer whose length is the sum of the lengths of init and *first.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 2
    Interesting though that it's defined using `+`, instead `init += *first;`, shouldn't that yield similar performance to the version with move? Is there really a valid case to prefer `+` and `move` over `+=` or is it just a design decision? – alagner Jan 01 '22 at 22:25
  • 5
    @alagner `std::accumulate` can't use `+=`, because it can't guarantee that `a += b` necessarily has the same semantics as `a = a + b` given that the user can overload these operators however they wish. If you want to write your own version with `+=`, that is a fine choice but you need to document it. If you're writing a non-generic version that just works with `std::string`, then you probably won't use `+` or `+=` at all since there are more efficient approaches to concatenation. – Brian Bi Jan 02 '22 at 02:25
  • @BrianBi That's a "Doctor, it hurts if I do this" problem. If a user defines `operator +=` to have different semantics from `a=a+b` they get what they deserve. – Spencer Jan 02 '22 at 14:47
  • 2
    @Spencer pretend that there's one overload and `BinaryOperation` is defaulted to `std::plus` if there were `std::plus_assign` we might have a different `std::accumulate` – Caleth Jan 02 '22 at 14:54
  • @Caleth That's a _much_ better argument. Perhaps even an answer. – Spencer Jan 02 '22 at 15:02
21

The value category of init + *first doesn't matter.

init in init + *first is a lvalue.

So if init + *first calls an operator+ overload taking the parameter by-value, it will cause a copy construction of that parameter

But the value of init is not required anymore after init + *first, so it makes sense to move it into the parameter instead.

Similarly a operator+ overload taking its first argument by rvalue-reference might be used to allow modification of the argument by the operation.

This is what std::move achieves here.

The standard specifies this behavior since C++20.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • But the `operator +` undoes any of this effect. It's going to return a pure rvalue which will be copied. – Spencer Jan 02 '22 at 14:41
  • 1
    @Spencer If `operator+` returns by-value, then `std::move(init) + *first` is a prvalue expression (independently of the `std::move`) and `=` will therefore use move assignment if available for the type. In the end, only moves are required, no copies. – user17732522 Jan 02 '22 at 14:44
4

It's nothing to do with the return value of operator +, but with the arguments to it.

As far as I can tell, a valid implementation of std::accumulate could be

template<typename ITER, typename T, typename OP = plus>
constexpr inline T accumulate(ITER first, ITER last, T init, OP op = {})
{
  for (; first != last; ++first)
      init = op(std::move(init), *first); // move the argument that is being overwritten

  return init;
}

It would be a larger breaking change to specify accumulate in terms of operator +=. For symmetry you'd want to change the BinaryOperation overloads, and that would break all the existing uses, as would any type that defined + but not += .

Caleth
  • 52,200
  • 2
  • 44
  • 75
-2

I was just reminded, when I wrote out an example of bignum addition yesterday as a lesson, why this is more efficient.

The binary + operator creates and returns a temporary object. With operands that fit into a machine register, this can be zero-cost (at most spilling what was in the destination register before onto the stack), but sometimes, a large data structure will need to be created. And this seems to be template code that might have to implement that use case.

If the left operand can be clobbered, though, + can be implemented as +=, overwriting the operand and optimizing away the creation of a new copy.

This coding style strikes me as a bit odd—as I mentioned, += has exactly the semantics the programmer seems to want here, so I’m not sure why they don’t use that instead.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • @YSC I’ve removed the misleading sentence. – Davislor Jan 02 '22 at 19:37
  • Since the comments have been cleaned up, the reason for the downvotes is that I originally referred to an expandable template argument as a “macro.” In C++, that term normally only is used for preprocessor macros – Davislor Jan 08 '22 at 22:13