Why does accumulate in C++ have two templates defined

Viewed 508
3 Answers

Because that's how the standard has been specified.

It is often a matter of taste whether to use an overload or a default argument. In this case, overload was chosen (by committee, by Alexander Stepanov, or by whoever happened to be responsible for the choice).

Default values are more limited than overloads. For example, you can have a function pointer T (*)(InputIterator, InputIterator, T) pointing to the first overload, which would not be possible if there was only one function (template) with 4 arguments. This flexibility can be used as an argument for using overloads rather than default arguments when possible.

It's true you would get mostly the same behavior from a single template like

template <class InputIt, class T, class BinaryOperation = std::plus<>>
accumulate(InputIt first, InputIt last, T init, BinaryOperation op = {});

But note that in earlier versions of C++, this would be difficult or impossible:

  • Prior to C++11, a function template could not have default template arguments.
  • Prior to C++14, std::plus<> (which is the same as std::plus<void>) was not valid: the class template could only be instantiated with one specific argument type.
  • The accumulate template is even older than the first C++ Standard of 1998: it goes back to the SGI STL library. At that time, compiler support for templates was rather inconsistent, so it was advisable to keep templates as simple as possible.

So the original two declarations were kept. As noted in bobah's answer, combining them into one declaration could break existing code, since for example code might be using a function pointer to an instantiation of the three-argument version (and function pointers cannot represent a default function argument, whether the function is from a template or not).

Sometimes the Standard library will add additional overloads to an existing function, but usually only for a specific purpose that would improve the interface, and when possible without breaking old code. There hasn't been any such reason for std::accumulate.

(But note member functions in the standard library can change more often than non-member functions like std::accumulate. The Standard gives implementations permission to declare member functions with different overloads, default arguments, etc. than specified as long as the effects are as described. This means it's generally a bad idea to take pointers to member functions to standard library class members, or otherwise assume very specific declarations, in the first place.)

The motivtion for the 2 functions is the same reason that we have both a copy and a transform function, to give the coder the flexability to apply a function on a per element basis. But perhaps some real world code would be helpful in understanding where this would be used. I've used both these snipits professionally in coding:

The 1st instance of accumulate can be used to sum the elements of a range. For example, given const int input[] = { 13, 42 } I can do this to get the sum of all elements in input:

accumulate(cbegin(input), cend(input), 0) /* Returns 55 */

I personally most commonly use the 2nd instance to generate strings (because it's the closest thing has to a join) but it can also be used when special preprocessing is needed before the element is added. For example:

accumulate(next(cbegin(input)), cend(input), to_string(front(input)), [](const auto& current_sum, const auto i){ return current_sum + ", " + to_string(i); }) /* Returns "13, 42"s */

It's worth noting P0616R0 when considering my use of the 2nd function. This proposal has been accepted into and will move rather than copy the first parameter to accumulate's functor, which, "Can lead to massive improvements (particularly, it means accumulating strings is linear rather than quadratic)."

Related