The <numeric> header collects a small family of algorithms for folding, scanning, and generating numeric sequences: summing a range, computing a dot product, building prefix sums, and filling a range with successive values. They sit alongside the <algorithm> algorithms but stay in a separate header because they were originally aimed at numeric work. This chapter covers each one with its common pitfalls, the C++17 parallel-friendly counterparts (reduce, transform_reduce, the scans), and the small math helpers gcd and lcm. The general-purpose <algorithm> functions (sort, find, transform, and friends) live in the next set of chapters.
std::accumulate(first, last, init) walks a range and folds it into a single value, starting from init. With no operator supplied, it sums the elements: it returns init + a[0] + a[1] + ... + a[n-1]. A common use is summing a cart of prices.
The third argument, init, matters more than it looks. It does two jobs at once: it gives the algorithm a starting value (often 0 for sums or 1 for products), and it dictates the type of the running accumulator. Whatever type init is, that's the type used to accumulate. If the elements are wider or more precise than the init type, the algorithm narrows during the fold without warning.
A common bug: initialising with 0 (an int) on a range of double makes the running total an int that drops the fractional parts on every add.
With 0 as the init, the accumulator is int. Each double price is added to the int and the fractional component is discarded on every step. The 95 cents that should have been there are gone, and the result is off by more than a dollar. The fix is one character: write 0.0 so the accumulator stays double.
accumulate is O(n) and runs sequentially in the order the elements appear. The default operator (+) for floating-point is not associative, so the order matters for exact rounding behaviour. To let the compiler reorder operations for speed, use std::reduce instead.
The same trap shows up with long long totals over int elements that might overflow. A range of int prices in cents can pass INT_MAX before reaching the end. The fix is the same: pick an init type that's wide enough.
0LL is a long long literal, so the accumulator is long long and can hold values past 2 billion. Initialising with plain 0 overflows the running sum around the 22nd day, producing a wrong (and undefined-behaviour) result.
accumulate also takes an optional fourth argument: a binary operation that combines the running value with the next element. The default is std::plus<>{}, but anything that takes (Acc, Elem) and returns Acc works.
The product version starts at 1 (the multiplicative identity) and multiplies elements in. The max version starts at the first element and keeps the larger value as it walks the range. Function objects like std::multiplies<> live in the <functional> header.
C++17 added std::reduce, which looks almost identical to accumulate but with one crucial difference: it makes no promise about the order in which elements are combined. The standard says the operation must be commutative and associative, and in return the implementation is free to reorder, parallelise, or even compute partial results in chunks.
The first call runs sequentially; the second uses std::execution::par from <execution> to allow parallel execution. The standard library may chunk the range across threads, reduce each chunk, then combine the partial results. For a million floating-point sums the parallel version can be several times faster on a multi-core machine.
The order-of-operations contract differs. accumulate is strictly left-to-right: ((init + a[0]) + a[1]) + a[2]. reduce may compute (a[0] + a[1]) + (a[2] + a[3]) or any other tree. For integer addition this is fine; for floating-point it can produce slightly different rounding. For non-commutative operations (string concatenation, subtraction), use accumulate not reduce.
String concatenation isn't commutative: "a" + "b" isn't "b" + "a". Using reduce here would be a bug, because the implementation could legitimately reorder the joins and produce "popular,new,sale". Stick with accumulate whenever the order of operations affects the result.
std::reduce with no execution policy still runs sequentially, just with a weaker ordering contract than accumulate. With std::execution::par, the implementation may use threads; parallelism doesn't kick in for free, and small ranges are usually slower under par than sequentially because of thread setup overhead.
std::transform_reduce is the fused version of transform followed by reduce. It takes one or two input ranges, a transform operation, and a reduction operation, and combines them in a single pass. The single-range form:
It applies unary_op to each element, then folds those results with binary_op starting from init. The two-range form is closer to a dot product.
The lambda extracts price * quantity from each cart item, and the + reduces those into a total. The same thing could be written as accumulate with a custom binary op, but transform_reduce keeps the "what each element contributes" and "how contributions combine" cleanly separated, and it can also be parallelised with an execution policy.
Both operations passed to transform_reduce must satisfy the same rules as reduce: the binary op should be commutative and associative when an execution policy is in play.
std::inner_product(first1, last1, first2, init) computes the dot product of two ranges. It walks both ranges in parallel and returns init + a[0]*b[0] + a[1]*b[1] + ....
The first two iterators define the first range; the third iterator is the start of the second range, which is assumed to be at least as long as the first. There's no end2 argument, so the caller must ensure the second range has enough elements. Reading past the end is undefined behaviour, the same as with any iterator pair.
The generalised form takes two binary operations: one for the "combine pairs" step (default *) and one for the "fold the combined values" step (default +).
A non-default example: checking whether two ratings vectors agree on every entry. The "combine" step is ==, producing a bool for each pair, and the "fold" step is &&, which is true only if every pair matches.
Read the calls in this order: for each position i, compute equal_to(a[i], b[i]), then combine all those bools with logical_and, starting from true. The result is true only if every pair was equal.
inner_product is strictly left-to-right, with no parallel variant. Use std::transform_reduce(first1, last1, first2, init, binary_op1, binary_op2) for the same shape with the option of parallel execution.
std::iota(first, last, value) fills a range with value, value+1, value+2, and so on. It's named after the Greek letter the APL language used for the same operation. A common use is creating an index vector or product-id sequence without writing a loop.
The size of the output range is set before the call. iota does not insert or grow the container; it overwrites existing elements. If the vector is empty, the call does nothing. The element type only needs to support ++value (pre-increment), so iota works with anything increment-able: int, long long, char, even iterators.
A pattern that comes up in sorting is building a permutation of indices, then sorting the indices by some external criterion. The indices give a sorted view of the original data without moving it.
iota fills idx with 0, 1, 2, 3, 4. std::sort then reorders those indices using the lambda, which compares the underlying prices. The original prices vector is untouched, but idx now describes the sorted order. This pattern appears often in performance-sensitive code where copying or moving the underlying records is expensive.
iota is O(n) and writes a value per element. It does no allocation; the container must already be sized.
std::partial_sum(first, last, dest) writes the prefix sums of the input range to the destination range. The output at position i is a[0] + a[1] + ... + a[i]. The same idea applies to a daily revenue series turned into cumulative revenue, or a list of refunds turned into a running balance.
The destination range must have at least as many elements as the input. A common shortcut is to use the input range itself as the destination (in-place prefix sum), which is allowed as long as the ranges don't overlap in conflicting ways.
partial_sum also takes an optional binary operator. With std::multiplies<>{}, you get a running product instead of a running sum, which is useful for things like compound discount factors.
After 5 days of stacking 5% then 10% discounts, the effective multiplier is about 0.73, meaning the price is about 73% of the original.
partial_sum is O(n). It's strictly sequential because each output depends on the previous one. For a parallel version, use inclusive_scan.
std::adjacent_difference is the inverse shape of partial_sum. It writes the differences between consecutive elements: the first output is a[0] itself, and each later output is a[i] - a[i-1]. Applying partial_sum to the result reproduces the original range.
The first output (0) is cumulativeOrders[0]. Each subsequent value is the difference with the previous: 4 - 0, 9 - 4, 15 - 9, and so on. The "today's number minus yesterday's number" pattern is what this is for.
Like partial_sum, adjacent_difference accepts a custom binary operation that replaces the default subtraction. Use it when "difference" means something other than minus, like a ratio.
C++17 added two scan algorithms that overlap heavily with partial_sum. The names refer to whether the current element is included in its own output.
| Algorithm | Output at position i | Parallel support |
|---|---|---|
partial_sum | a[0] + ... + a[i] (inclusive) | None |
inclusive_scan | a[0] + ... + a[i] (inclusive) | Yes, with execution policy |
exclusive_scan | init + a[0] + ... + a[i-1] (excludes a[i]) | Yes, with execution policy |
The inclusive scan matches partial_sum. The exclusive scan starts from the init value (0 here) and shifts everything one slot to the right, so each output is the prefix sum up to but not including the current element. Exclusive scans are useful when each output represents "the running total before processing this item."
Both scans accept an execution policy. On large ranges with parallel-safe operations, that can provide a real speedup.
The parallel scan splits the range, computes partial scans on each chunk, then fixes up the offsets in a second pass. The total work is more than the sequential version, but on big enough ranges the wall-clock time is shorter on a multi-core machine.
Sequential scans are O(n) with one add per element. Parallel scans are roughly O(n) work and O(log n) span on p cores; extra arithmetic in exchange for shorter latency. Don't use the parallel version on small ranges; the overhead outweighs the gain.
<numeric> also includes the small integer-theory helpers std::gcd and std::lcm, both added in C++17. They compute the greatest common divisor and least common multiple of two integers.
These are useful for ratio simplification, finding common payment intervals, or any place that calls for a Euclidean algorithm. They work on any integer types, returning the common type of the two arguments. Both are constexpr, so the result is computable at compile time when the inputs are.
A practical use: simplifying a rating fraction like "27 out of 30" into "9 out of 10."
Both arguments are divided by their GCD, which produces the lowest-terms form of the fraction.
A small cart calculator that uses several of the numeric algorithms in one place: iota to assign line numbers, inner_product for the subtotal, partial_sum for the running tally, and accumulate for the average price.
Each piece does one thing. iota numbers the lines. inner_product computes the dot product of quantities and prices. partial_sum produces the running total. accumulate produces the average. The code is shorter and clearer than a single loop that mixes all four concerns.
The numeric algorithms overlap enough that the choice can be confusing. A table summarising the decision:
| What you want | Use |
|---|---|
| Sum or fold a range, strictly left-to-right | std::accumulate |
| Sum a range with a parallel-safe op | std::reduce |
| Map each element then fold, optionally parallel | std::transform_reduce |
| Dot product or two-range fold | std::inner_product |
Fill with value, value+1, value+2, ... | std::iota |
| Running sum, strictly sequential | std::partial_sum |
| Running sum, optionally parallel | std::inclusive_scan |
| Running sum where each output excludes its own element | std::exclusive_scan |
| Successive differences | std::adjacent_difference |
| Integer GCD / LCM | std::gcd, std::lcm |
When in doubt about whether the operation is associative and commutative, prefer accumulate. The cost of "maybe wrong under reordering" is much higher than the cost of "definitely sequential." Use reduce and the parallel scans only after confirming both that the operation is safe and that the range is large enough to make parallelism pay.
The flowchart picks the algorithm from the shape of the work, not the data type. Once the shape (folding, scanning, or differencing) is clear, the data type only determines the init and the operator.
10 quizzes