Use of std::move in std::accumulate

Viewed 1405

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 ?

4 Answers

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.

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.

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 += .

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.

Related