A detailed guide to generating a single object from collections using the

`std::reduce()`

and `std::accumulate()`

algorithmsUpdated

In this lesson and the next, we introduce a range of algorithms that are designed to simplify large collections of objects into simplerÂ outputs.

For example, we might have a collection of objects representing bank transactions, and we want to generate a simple object that includes some aggregate data. That could perhaps include informationÂ like:

- What was the total amount of all transactions
- How many transactions were there
- What is the average transaction value

In this lesson, weâ€™ll introduce the `std::reduce()`

and `std::accumulate()`

algorithms, which remain the most popular way of implementing logic likeÂ this.

In the next lesson, weâ€™ll introduce * fold algorithms*, which were added to the language in C++23, and give us more ways to accomplish tasks likeÂ this.

`std::reduce()`

The `std::reduce()`

algorithm is available within the standard libraryâ€™s `<numeric>`

Â header.

The most basic form of a `std::reduce()`

call involves two iterators, denoting the first and last element of ourÂ input.

The algorithm will return the result of adding all the objects in that rangeÂ together:

```
#include <numeric>
#include <iostream>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3, 4, 5};
int Result{
std::reduce(Numbers.begin(),
Numbers.end())};
std::cout << "Result: " << Result;
}
```

`Result: 15`

`std::reduce()`

Initial Value and Operators`std::reduce()`

also has an overload that accepts an additional two arguments, so four inÂ total:

- An iterator pointing at the beginning of the input
- An iterator pointing at the end of the input
- An initial value to use for the algorithm (we cover this in more detail later)
- A function / callable that determines how our objects are combined. It accepts two objects from our input as arguments and returns a single value

We can recreate the default behavior of adding everything in the range by passing `0`

as the initial value (third argument) and a function that implements the addition operator as the fourthÂ argument.

The standard library includes `std::plus`

, which can help us with this. It returns a callable that simply accepts two arguments and returns the result of calling the `+`

operator withÂ them:

```
#include <numeric>
#include <iostream>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3, 4, 5};
int Result{
std::reduce(Numbers.begin(),
Numbers.end(), 0,
std::plus{}
)
};
std::cout << "Result: " << Result;
}
```

`Result: 15`

Below, we change the behavior of `std::reduce()`

to multiply the values in our input, rather than addÂ them:

```
#include <numeric>
#include <iostream>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3, 4, 5};
int Result{
std::reduce(Numbers.begin(),
Numbers.end(), 1,
std::multiplies{}
)};
std::cout << "Result: " << Result;
}
```

`Result: 120`

With multiplication, we set the initial value to `1`

, as that is the * identity value* forÂ multiplication.

With algorithms like `std::reduce()`

, the value we tend to pass as the initial value tends to be the identity of that operation. The identity is the value that causes the operator to return an object that is equal to the * other operand*.

For example, the identity of addition is $0$, as $n + 0 = n$. The multiplication identity is $1$, as $n \times 1 =Â n$.

Other, more complex operators may have different identityÂ values.

If the operator weâ€™re using with `std::reduce()`

doesnâ€™t have an identity or we donâ€™t know what it is, we have a way to get around that. We can set our initial value to the first object of our input, and then exclude it from the rest of theÂ algorithm.

It could look something like the below, where our initial value is the first number in our `std::vector`

, and our first argument to `std::reduce()`

excludes that value, by advancing the iterator pastÂ it:

```
#include <numeric>
#include <iostream>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3, 4, 5};
int Result{
std::reduce(Numbers.begin() + 1,
Numbers.end(),
Numbers[0],
std::plus{}
)};
std::cout << "Result: " << Result;
}
```

`Result: 15`

This approach assumes our input has at least one value. If our input could have a size of `0`

, we can check for that before running the algorithm, and handle it in whatever way makes sense for ourÂ program.

C++23 added some alternatives to `std::reduce()`

which can take care of this edge case for us. These are called * fold algorithms*, and we introduce them in the nextÂ lesson.

`std::reduce()`

Of course, for the operator argument of `std::reduce()`

, weâ€™re not just limited to what is in the standard library. We can provide any callable (eg, a function, lambda, or functor) and pass it as the fourthÂ argument.

Below, we pass an operator that will add the * absolute* values of the objects in ourÂ input:

```
#include <iostream>
#include <numeric>
#include <vector>
int main(){
std::vector Nums{1, -2, 3, -4, 5};
int Result{
std::reduce(
Nums.begin(), Nums.end(), 0,
[](int x, int y){
return std::abs(x) + std::abs(y);
}
)
};
std::cout << "Result: " << Result;
}
```

`Result: 15`

`std::reduce()`

Multithreading and Non-Deterministic ResultsThe `std::reduce()`

algorithm is designed to be usable in multi-threaded environments. We cover multi-threading in detail later in the course, but for now, there is an implication we should be awareÂ of.

Specifically, we cannot assume the objects in our input are always combined in the sameÂ order.

For example, if our operator function is `func`

and our input is a collection comprising of `a`

, `b`

, and `c`

, the `std::reduce()`

algorithm might return the resultÂ of:

`func(a, func(b, c))`

`func(b, func(a, c))`

`func(func(c, a), b)`

- or any other permutation

As such, `std::reduce()`

is most commonly used with an operator that will return the same result, regardless of how its operands are combined or grouped. The words * commutative* and

A * commutative operation* gives the same result, regardless of which operand is on the left and which is on theÂ right.

Addition is an example of a commutative operation because $A + B$ is equivalent to $B +Â A$.

Subtraction is * not* commutative, because $A - B$ is not necessarily equivalent to $B -Â A$.

An * associative operation* gives the same result, regardless of how individual operations are grouped within a largerÂ expression.

Addition is an example of an associative operation because $(A + B) + C$ is equivalent to $A + (B +Â C)$.

Subtraction is * not* associative, because $(A - B) - C$ is not necessarily equivalent to $A - (B -Â C)$.

For example: $(1 - 2) - 3 = -4$ but $1 - (2 - 3) =Â 2$

If the operator we use with `std::reduce()`

is not commutative or not associative, the algorithm will be * non-deterministic*. That is, it may give a different return value each time it is run, even though the inputs are theÂ same.

`std::accumulate()`

`std::accumulate()`

is also available within `<numeric>`

, and has a very similar use case as `std::reduce()`

. The key difference is that `std::accumulate()`

guarantees that the operands in our input range are combined in a consistent order - left toÂ right.

This means its output will be deterministic, even if the operator isnâ€™t commutative orÂ associative.

The trade-off is this guaranteed sequencing is that `std::accumulate()`

is single-threaded so, for larger tasks, it can be slower than `std::reduce()`

Similar to `std::reduce()`

, the `std::accumulate()`

algorithm combines elements using the `+`

operator by default. The only difference is that the initial value isnâ€™tÂ optional:

```
#include <iostream>
#include <numeric>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3, 4, 5};
int Result{
std::accumulate(Numbers.begin(),
Numbers.end(), 0)};
std::cout << "Result: " << Result;
}
```

`Result: 15`

We can change the operator in the usual way, by providing a 4th argument, and updating the initial value (the third argument) ifÂ needed:

```
#include <iostream>
#include <numeric>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3, 4, 5};
int Result{
std::accumulate(Numbers.begin(),
Numbers.end(), 1,
std::multiplies{})};
std::cout << "Result: " << Result;
}
```

`120`

Because of the guaranteed sequencing, `std::accumulate()`

makes it easy to return a type that is different from the types of ourÂ input.

The type that will be returned is the type of our initial value - the thirdÂ argument.

Below, we accumulate our integers to aÂ float:

```
#include <iostream>
#include <numeric>
#include <vector>
int main(){
std::vector Nums{1, 2, 3};
std::cout << std::accumulate(
Nums.begin(), Nums.end(), 0.5f);
};
```

`6.5`

When providing a custom operator for a scenario where the input and output types are different, itâ€™s worth reviewing what its signature willÂ be:

- Its first parameter will have the type of the initial value - what we passed as the third argument to
`std::accumulate()`

- Its second parameter will have the type of our input objects
- Its return type will be the same as the first parameter type / initial value

The following example includes a lambda that implements the correct signature where weâ€™re accumulating `int`

objects to a `float`

:

```
#include <iostream>
#include <numeric>
#include <vector>
int main(){
std::vector Nums{1, -2, 3};
auto Fun{
[](float f, int i) -> float{
return std::abs(f) + std::abs(i);
}};
std::cout << std::accumulate(
Nums.begin(), Nums.end(), 0.5f, Fun);
};
```

`6.5`

In this more complex example, we accumulate our integers into a custom `Accumulator`

Â type:

```
#include <iostream>
#include <numeric>
#include <vector>
struct Accumulator {
int Total{0};
int Count{0};
static Accumulator Add(Accumulator A, int V){
return {A.Total + V, A.Count + 1};
}
void Log(){
std::cout
<< "Count: " << Count
<< "\nSum: " << Total;
if (Count > 0) {
std::cout
<< "\nAverage: "
<< static_cast<float>(Total) / Count;
}
}
};
int main(){
std::vector Nums{99, 65, 26, 72, 17};
std::accumulate(
Nums.begin(), Nums.end(),
Accumulator{}, Accumulator::Add
).Log();
};
```

```
Count: 5
Sum: 279
Average: 55.8
```

The `std::reduce()`

algorithm can also reduce objects to a different type, including a custom type. However, it can involve a bit more effort to ensure everything is set up to work deterministically in a multi-threadedÂ environment.

We cover those considerations in a dedicated section later in theÂ course.

The `std::accumulate()`

algorithm will always process elements in the input range from left to right. Many sequential containers over reverse iterators - `rbegin()`

and `rend()`

which allows `std::accumulate()`

(and any other iterator-based algorithm) to proceed in reverseÂ order:

```
#include <iostream>
#include <numeric>
#include <vector>
int main(){
std::vector Numbers{1, 2, 3};
auto Log{
[](int x, int y){
std::cout << y << ", ";
return 0;
}};
std::cout << "Forward: ";
std::accumulate(
Numbers.begin(), Numbers.end(), 0, Log);
std::cout << "\nReverse: ";
std::accumulate(
Numbers.rbegin(), Numbers.rend(), 0, Log);
}
```

```
Forward: 1, 2, 3,
Reverse: 3, 2, 1,
```

If reverse iterators are not available, we can also prepare our input using one of the other algorithms we covered earlier. For example, we can reverse our input using `std::ranges::reverse()`

, or randomize it using `std::ranges::shuffle()`

. We covered both of these in our earlier lesson on movementÂ algorithms:

C++23 includes `std::ranges::fold_left()`

, which is effectively equivalent to `std::accumulate()`

. We cover this and other fold algorithms in the nextÂ lesson.

A range-based variation of `std::reduce()`

is likely to come in a future C++Â version.

But for now, `std::reduce()`

and `std::accumulate()`

are iterator-based algorithms and are not directly compatible withÂ ranges.

However, even though they donâ€™t work with ranges directly, we can still use range-based techniques in the surrounding code to accomplish more complexÂ tasks.

For example, below, we use a * view* to create a range that only includes the odd numbers of our input - 1 and 3 in this case. We then use the iterators the view provides to

`accumulate()`

only those oddÂ numbers:```
#include <iostream>
#include <numeric>
#include <vector>
#include <ranges>
int main(){
std::vector Numbers{1, 2, 3};
auto V{
std::views::filter(Numbers, [](int i){
return i % 2 == 1;
})};
std::cout << "Result: "
<< std::accumulate(V.begin(), V.end(), 0);
}
```

`Result: 4`

In this lesson, we've introduced how to simplify collections into single values using the `std::reduce()`

and `std::accumulate()`

Â algorithms.

- The
`std::reduce()`

algorithm, introduced in C++17, allows for parallel reduction of a sequence of values using a binary operation, potentially in a non-deterministic order. `std::accumulate()`

operates sequentially from left to right, ensuring deterministic results even with non-commutative or non-associative operations, at the trade-off of being single-threaded.- Identity values and their importance in initializing the reduce and accumulate operations, with examples including 0 for addition and 1 for multiplication.
- The use of custom operators with
`std::reduce()`

and`std::accumulate()`

to perform complex accumulation tasks. - Techniques for accumulating to different types, including custom types, with
`std::accumulate()`

. - How to reverse the accumulation order with
`std::accumulate()`

using reverse iterators. - Introduction to range-based techniques and the anticipation of future C++ standards including range-based variations of these algorithms.

Was this lesson useful?

Updated

Lesson Contents### The Reduce and Accumulate Algorithms

A detailed guide to generating a single object from collections using the `std::reduce()`

and `std::accumulate()`

algorithms

This lesson is part of theÂ course:### Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.