Operations Supplied as Arguments
Accumulate computes an operation, such as a sum, over a sequence by visiting its elements from begin to end and building a running total from an initial value.
Predict Before You Read
Accumulate is a standard-library algorithm that adds up the elements of a sequence. Before reading any further, take a moment to make a guess. Imagine a list of four doubles, 1.0, 2.0, 3.0 and 4.0, and a call that passes the beginning of the list, the end of the list, and the starting value 0.0. What total do you expect the call to return, and which part of the call decides that total: the list, the two iterator arguments, or the 0.0?
Keep your guess in mind. The rest of this article explains exactly how the total is built, so you can check your prediction against the reasoning, step by step.
What Accumulate Computes
Accumulate computes an operation, such as a sum, over a sequence by visiting its elements from begin to end and building a running total from an initial value.
Two ideas in that definition do most of the work. The first is the running total: a single value that starts at the initial value and changes each time an element is taken in. The second is the traversal: the algorithm visits the elements in order, starting at the beginning of the sequence and moving toward its end. The final running total is the value the call returns.
Reading the Three Arguments
The source states that the three arguments are the start position, the end position, and the initial value, and that each one changes the result. Here is a call that follows the template. The list v holds the four values from the opening prediction.
total = 10.0v.begin()is the start position: the first element is includedv.end()is the end position: the traversal stops before it, so it is not an element that gets added- 0.0 is the initial value: the running total begins here before any element is added
Notice that the sum is 10.0 only because the initial value is 0.0. The initial value is not decoration: it is the first value of the running total, and it is carried into every later step. Change it, and every intermediate total changes with it, which is exactly why each argument matters.
Positions the Call Covers
The diagram below lays the list out by position. Read it as a map of which elements the call visits. For the full call from the previous section, every element from the first to the last is visited, and the end position sits just past the final element, where the traversal stops.
The begin bound is the position of v[0], the first element visited. The end bound is the position after v[3]. Moving the end bound back to the position of v[2], as in the partial call, removes v[2] and v[3] from the visit, which is why the result changes from 10.0 to 3.0 while the list itself stays the same.
Building the Running Total Step by Step
The clearest way to understand accumulate is to follow the running total through the list yourself. Each step takes the current total and adds the next element to it. Mathematically, if x_1 through x_n are the elements visited and init is the initial value, the running total s_k after visiting k elements follows this rule.
- 1$s_0$ is the running total before any element is visited, which is the initial value
- 2$x_k$ is the k-th element taken in from the beginning of the sequence
- 3$s_k$ is the running total after that element is added, and it is carried forward to the next step
- 4The returned result is the final running total after the last element has been visited
Tracing the total for 1.0, 2.0, 3.0, 4.0 starting at 0.0
Find each running total for accumulate(v.begin(), v.end(), 0.0) when v holds 1.0, 2.0, 3.0 and 4.0.
- 1Start
The running total begins at the initial value, 0.0. No element has been taken in yet.
- 2Visit 1.0
The total becomes 0.0 + 1.0 = 1.0. This value is carried forward.
- 3Visit 2.0
The total becomes 1.0 + 2.0 = 3.0.
- 4Visit 3.0
The total becomes 3.0 + 3.0 = 6.0.
- 5Visit 4.0
The total becomes 6.0 + 4.0 = 10.0. The end position has now been reached, so the traversal stops.
The call returns 10.0, the last running total in the trace.
Read the diagram from left to right. Each box holds the running total at that moment, and each arrow is one visit to an element. The total you finally get back is the value in the last box before the traversal stops. Your opening prediction can now be checked against this trace: the 0.0 sets where the running total starts, and the list supplies the values that are added to it.
Generalized Algorithms and Swappable Parts
The source describes accumulate as a generalized algorithm. Generalizing here means two things. First, the algorithm is not tied to one container, so it applies to every standard-library sequence. Second, it does not hard-wire the operation applied to the elements, because that operation can be supplied as an argument. The same pattern of visiting elements from begin to end, starting from an initial value, stays the same.
The version you have seen so far applies the most common operator for this algorithm, which is addition for a sum. The source explains that each generalized algorithm is supplemented by a version that applies the most common operator, which means a more general version also exists. In that general version, the operation is a parameter, so a different operation can be passed in instead of the built-in sum. The source points to a later section for the full details, so treat this section as orientation.
Because the three parts are separate, you can change one without rewriting the others. A different sequence can be passed where the list was, a different initial value can start the total, and a different operation can replace addition. The traversal from begin to end still defines the order in which the elements are taken in.
Finding Where a Result Goes Wrong
When an accumulate result looks wrong, the source's advice is to trace the running total step by step and find the first point where it disagrees with what you expected. Suppose you expected 10.0 but a call returned 110.0. Writing out both traces side by side shows exactly where they split.
The first disagreement is at the start: the expected trace begins at 0.0, while the actual call begins at 100.0. Every later step inherits that difference, so the final total is off by exactly 100.0. Here the cause is the initial value, not the list and not the operation. Tracing the total in this way tells you which argument to inspect first.
Common mistake Treating the initial value as a detail that can be ignored
What it looks likeaccumulate(v.begin(), v.end(), 100.0) over 1.0, 2.0, 3.0, 4.0 returns 110.0, not 10.0.Why it failsThe source states that each argument changes the result. The initial value is the first running total and is carried into every later step.
Do this insteadName the initial value as you read the call and check it first when the total is off by a constant amount.
Common mistake Assuming the end bound is an element that gets added
What it looks likeaccumulate(v.begin(), v.begin() + 2, 0.0) returns 3.0, not 10.0, because the traversal stops before the element at that position.
Why it failsThe end position marks where the visit stops, so elements at and after it are not included.
Do this insteadCount the elements from the begin position up to, but not including, the end position.
Common mistake Assuming the list determines the whole answer
What it looks likeTwo calls over the same list with different initial values give totals that differ by the difference between those initial values.
Why it failsThe list supplies the elements, but the three arguments together decide the result.
Do this insteadCompare all three arguments of two calls before concluding that the list is the cause.
Practice Tracing a Total
A list w holds 2.0, 5.0 and 1.0. Predict the value of accumulate(w.begin(), w.end(), 0.0) by writing the running total after each element. Then check whether your answer changes if the initial value is 10.0 instead.
Hints
- Start the running total at the initial value, 0.0, and add 2.0 first.
- Write each intermediate total on its own line, so you can compare it with the expected value at that step.
- With the initial value 10.0, every intermediate total should be 10.0 larger than before.
Using the same list w, find the end position that makes accumulate(w.begin(), ??, 0.0) return 2.0. Explain which element is excluded and why the result matches your trace.
Hints
- The result 2.0 comes from only the first element of w.
- The end position must sit just past the first element, so it is the position of the second element.
Key Takeaways
- Accumulate computes an operation, such as a sum, over a sequence by visiting its elements from begin to end and building a running total from an initial value.
- The three arguments are the start position, the end position, and the initial value, and each one changes the result.
- Because the end position marks where the visit stops, only the elements from the begin position up to that point are included.
- Generalized algorithms work on every standard-library sequence and let the operation be supplied as an argument, so the sequence, operation and initial value are separate parts.
- When a result looks wrong, trace the running total step by step to find the first step where it disagrees with your expectation, then inspect the argument responsible.