C++ transform accumulate STL algorithms tutorial
|

C++ Transform, Accumulate & More: STL Algorithms Deep Dive 2026

1. Beyond sort() and find()

In the previous lesson on sorting and searching, you learned the most common STL algorithms. But the <algorithm> and <numeric> headers contain over 100 algorithms that can replace manual loops, making your code shorter, safer, and often faster.

This lesson covers the algorithms you’ll use daily — transforming data, reducing collections to single values, filtering elements, and finding extremes. These are the building blocks of expressive, modern C++.

2. std::transform — Map Operations

std::transform applies a function to each element and stores the result. It’s the C++ equivalent of “map” in functional programming:

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>

int main() {
    // Unary transform: square each number
    std::vector<int> nums = {1, 2, 3, 4, 5};
    std::vector<int> squares(nums.size());

    std::transform(nums.begin(), nums.end(), squares.begin(),
                   [](int n) { return n * n; });
    // squares: {1, 4, 9, 16, 25}

    // Transform in-place
    std::transform(nums.begin(), nums.end(), nums.begin(),
                   [](int n) { return n * 2; });
    // nums: {2, 4, 6, 8, 10}

    // Binary transform: add two vectors element-wise
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = {10, 20, 30};
    std::vector<int> sums(3);

    std::transform(a.begin(), a.end(), b.begin(), sums.begin(),
                   [](int x, int y) { return x + y; });
    // sums: {11, 22, 33}

    // Transform strings to uppercase
    std::string text = "hello world";
    std::transform(text.begin(), text.end(), text.begin(),
                   [](char c) { return std::toupper(c); });
    // text: "HELLO WORLD"

    for (int s : squares) std::cout << s << " ";
    std::cout << "\n" << text << std::endl;
}

Key points about std::transform:

  • The output range must be pre-allocated (or use std::back_inserter)
  • Unary form takes one input range; binary form takes two
  • Can transform in-place when output iterator equals input begin
  • Returns iterator past the last transformed element

3. std::accumulate — Fold/Reduce

std::accumulate (from <numeric>) reduces a range to a single value by repeatedly applying a binary operation:

#include <numeric>
#include <vector>
#include <string>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};

    // Sum (default operation is +)
    int sum = std::accumulate(nums.begin(), nums.end(), 0);
    // sum = 15

    // Product
    int product = std::accumulate(nums.begin(), nums.end(), 1,
                                   [](int acc, int val) { return acc * val; });
    // product = 120

    // Concatenate strings
    std::vector<std::string> words = {"Hello", " ", "World", "!"};
    std::string sentence = std::accumulate(
        words.begin(), words.end(), std::string{},
        [](const std::string& acc, const std::string& w) {
            return acc + w;
        });
    // sentence = "Hello World!"

    // Find maximum using accumulate
    int maxVal = std::accumulate(nums.begin(), nums.end(), nums[0],
                                  [](int a, int b) { return std::max(a, b); });

    // Count elements matching condition
    int evens = std::accumulate(nums.begin(), nums.end(), 0,
                                 [](int count, int val) {
                                     return count + (val % 2 == 0 ? 1 : 0);
                                 });

    std::cout << "Sum: " << sum << "\n";
    std::cout << "Product: " << product << "\n";
    std::cout << "Sentence: " << sentence << "\n";
    std::cout << "Max: " << maxVal << "\n";
    std::cout << "Evens: " << evens << std::endl;
}
Warning: Watch the initial value type! std::accumulate(v.begin(), v.end(), 0) uses int arithmetic even if v contains double. Use 0.0 for floating-point sums.

C++17 added std::reduce which is similar but allows parallel execution:

#include <numeric>
#include <execution>

// Parallel sum (C++17)
int sum = std::reduce(std::execution::par, nums.begin(), nums.end(), 0);
// Note: binary op must be associative and commutative for parallel reduce

4. std::for_each — Apply to All

std::for_each applies a function to every element. Unlike range-for loops, it works with iterator pairs and returns the function object:

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};

    // Print each element
    std::for_each(nums.begin(), nums.end(),
                  [](int n) { std::cout << n << " "; });
    std::cout << "\n";

    // Modify in-place (pass by reference)
    std::for_each(nums.begin(), nums.end(),
                  [](int& n) { n *= 10; });
    // nums: {10, 20, 30, 40, 50}

    // Use a stateful functor to accumulate info
    struct Stats {
        int count = 0;
        double sum = 0;
        void operator()(int n) { ++count; sum += n; }
    };

    Stats result = std::for_each(nums.begin(), nums.end(), Stats{});
    std::cout << "Count: " << result.count
              << ", Avg: " << result.sum / result.count << std::endl;

    // for_each on a sub-range
    std::for_each(nums.begin(), nums.begin() + 3,
                  [](int n) { std::cout << n << " "; });
    // Output: 10 20 30
}

C++17 added std::for_each_n which processes exactly N elements:

std::for_each_n(nums.begin(), 3, [](int& n) { n += 1; });

5. std::copy_if & std::remove_if

These algorithms filter elements — copy_if copies matching elements to a new container, while remove_if shuffles non-matching elements to the end:

#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // copy_if: filter evens into a new vector
    std::vector<int> evens;
    std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens),
                 [](int n) { return n % 2 == 0; });
    // evens: {2, 4, 6, 8, 10}

    // remove_if + erase (the erase-remove idiom)
    std::vector<int> data = {1, -2, 3, -4, 5, -6};
    auto newEnd = std::remove_if(data.begin(), data.end(),
                                  [](int n) { return n < 0; });
    data.erase(newEnd, data.end());
    // data: {1, 3, 5}

    // C++20 std::erase_if simplifies this:
    std::vector<int> vals = {10, 15, 20, 25, 30};
    std::erase_if(vals, [](int n) { return n > 20; });
    // vals: {10, 15, 20}

    // copy_n: copy first N elements
    std::vector<int> first3;
    std::copy_n(nums.begin(), 3, std::back_inserter(first3));
    // first3: {1, 2, 3}

    // partition: reorder so predicate-true elements come first
    std::vector<int> mixed = {5, 1, 4, 2, 3};
    auto pivot = std::partition(mixed.begin(), mixed.end(),
                                 [](int n) { return n <= 3; });
    // Elements <= 3 now before pivot, > 3 after
    // e.g., {1, 2, 3, 5, 4} (relative order not preserved)

    for (int e : evens) std::cout << e << " ";
    std::cout << std::endl;
}
Key Insight: std::remove_if doesn’t actually remove elements — it moves non-matching elements to the front and returns an iterator to the new logical end. You must call erase() to actually shrink the container. C++20’s std::erase_if does both in one step.

6. std::min_element, max_element, minmax

Finding extreme values without manual loops:

#include <algorithm>
#include <vector>
#include <iostream>

struct Student {
    std::string name;
    double gpa;
};

int main() {
    std::vector<int> nums = {42, 17, 93, 5, 68};

    auto minIt = std::min_element(nums.begin(), nums.end());
    auto maxIt = std::max_element(nums.begin(), nums.end());
    std::cout << "Min: " << *minIt << " at index "
              << std::distance(nums.begin(), minIt) << "\n";
    std::cout << "Max: " << *maxIt << "\n";

    // minmax_element: find both in one pass (more efficient)
    auto [lo, hi] = std::minmax_element(nums.begin(), nums.end());
    std::cout << "Range: [" << *lo << ", " << *hi << "]\n";

    // With custom comparator
    std::vector<Student> students = {
        {"Alice", 3.8}, {"Bob", 3.2}, {"Charlie", 3.95}
    };

    auto best = std::max_element(students.begin(), students.end(),
                                  [](const Student& a, const Student& b) {
                                      return a.gpa < b.gpa;
                                  });
    std::cout << "Top student: " << best->name
              << " (GPA: " << best->gpa << ")\n";

    // std::clamp (C++17): constrain value to range
    int value = 150;
    int clamped = std::clamp(value, 0, 100);
    std::cout << "Clamped: " << clamped << std::endl; // 100
}

7. Numeric Algorithms

The <numeric> header has powerful algorithms for mathematical operations:

#include <numeric>
#include <vector>
#include <iostream>

int main() {
    // iota: fill with incrementing values
    std::vector<int> seq(10);
    std::iota(seq.begin(), seq.end(), 1);
    // seq: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // partial_sum: running total
    std::vector<int> running(seq.size());
    std::partial_sum(seq.begin(), seq.end(), running.begin());
    // running: {1, 3, 6, 10, 15, 21, 28, 36, 45, 55}

    // adjacent_difference
    std::vector<int> data = {1, 4, 9, 16, 25};
    std::vector<int> diffs(data.size());
    std::adjacent_difference(data.begin(), data.end(), diffs.begin());
    // diffs: {1, 3, 5, 7, 9}

    // inner_product (dot product)
    std::vector<double> a = {1.0, 2.0, 3.0};
    std::vector<double> b = {4.0, 5.0, 6.0};
    double dot = std::inner_product(a.begin(), a.end(), b.begin(), 0.0);
    // dot = 1*4 + 2*5 + 3*6 = 32.0

    // C++17: inclusive_scan and exclusive_scan
    // (parallel-friendly versions of partial_sum)
    std::vector<int> inclusive(seq.size());
    std::inclusive_scan(seq.begin(), seq.end(), inclusive.begin());
    // Same as partial_sum for addition

    // C++17: transform_reduce (map + reduce in one)
    std::vector<int> prices = {10, 20, 30};
    std::vector<int> quantities = {2, 3, 1};
    int total = std::transform_reduce(
        prices.begin(), prices.end(), quantities.begin(), 0);
    // total = 10*2 + 20*3 + 30*1 = 110

    std::cout << "Dot product: " << dot << "\n";
    std::cout << "Total: " << total << std::endl;
}

8. Combining Algorithms

The real power emerges when you chain algorithms to build data pipelines:

#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>

struct Order {
    std::string product;
    double price;
    int quantity;
    bool fulfilled;
};

int main() {
    std::vector<Order> orders = {
        {"Widget", 9.99, 5, true},
        {"Gadget", 24.99, 2, false},
        {"Doohickey", 4.99, 10, true},
        {"Thingamajig", 14.99, 3, true},
        {"Widget", 9.99, 8, false}
    };

    // Pipeline: fulfilled orders → totals → sum
    // Step 1: Filter fulfilled orders
    std::vector<Order> fulfilled;
    std::copy_if(orders.begin(), orders.end(),
                 std::back_inserter(fulfilled),
                 [](const Order& o) { return o.fulfilled; });

    // Step 2: Calculate line totals
    std::vector<double> totals(fulfilled.size());
    std::transform(fulfilled.begin(), fulfilled.end(), totals.begin(),
                   [](const Order& o) { return o.price * o.quantity; });

    // Step 3: Sum all totals
    double revenue = std::accumulate(totals.begin(), totals.end(), 0.0);

    std::cout << "Fulfilled revenue: $" << revenue << "\n";

    // Or do it all in one with transform_reduce (C++17):
    double rev2 = std::transform_reduce(
        orders.begin(), orders.end(), 0.0,
        std::plus<>{},
        [](const Order& o) {
            return o.fulfilled ? o.price * o.quantity : 0.0;
        });
    std::cout << "Same result: $" << rev2 << "\n";

    // Sort by total value descending
    std::sort(orders.begin(), orders.end(),
              [](const Order& a, const Order& b) {
                  return (a.price * a.quantity) > (b.price * b.quantity);
              });

    // Count unfulfilled
    auto unfulfilled = std::count_if(orders.begin(), orders.end(),
                                      [](const Order& o) { return !o.fulfilled; });
    std::cout << "Unfulfilled: " << unfulfilled << "\n";

    // Check if all prices are positive
    bool allPositive = std::all_of(orders.begin(), orders.end(),
                                    [](const Order& o) { return o.price > 0; });
    std::cout << "All positive prices: " << std::boolalpha << allPositive << std::endl;
}

9. Practice Exercises

Exercise 1: Word Frequency Counter

Given a vector of strings, use STL algorithms to: count how many start with a vowel, transform all to lowercase, and find the longest word.

Exercise 2: Statistical Calculator

Use accumulate, transform, min_element, and max_element to compute mean, variance, standard deviation, min, and max of a dataset.

Exercise 3: Data Pipeline

Given a vector of Employee structs (name, department, salary), filter by department, calculate average salary, and find the employee closest to the average.

What’s Next?

Now that you can transform and reduce data with STL algorithms, you’re ready to learn Lambda Expressions — the feature that makes all these algorithms truly powerful with inline, flexible function objects.

You can also review the full C++ Learning Roadmap to see how algorithms fit into your journey.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *