Skip to content

STL Algorithms & Lambdas

The STL algorithms library <algorithm> is a collection of powerful, reusable functions that operate on ranges of elements. They decouple what you want to do from how it is done.

Common Algorithms

Sorting and Searching

#include <algorithm>
#include <vector>

std::vector<int> v = {5, 1, 4, 2, 3};

// Sort in ascending order
std::sort(v.begin(), v.end()); // 1, 2, 3, 4, 5

// Binary search (requires sorted range)
bool found = std::binary_search(v.begin(), v.end(), 3);

Modifying Sequences

1
2
3
4
5
6
7
// Fill with a value
std::fill(v.begin(), v.end(), 0);

// Transform (Map)
std::transform(v.begin(), v.end(), v.begin(), [](int n) {
    return n * n; // Square each element
});

Non-Modifying Operations

// Count occurrences
int count = std::count(v.begin(), v.end(), 0);

// Find an element
auto it = std::find(v.begin(), v.end(), 42);
if (it != v.end()) {
    // Found
}

// Numeric Operations (requires <numeric>)
int sum = std::accumulate(v.begin(), v.end(), 0);

// Logical Operations
bool all_even = std::all_of(v.begin(), v.end(), [](int i){ return i % 2 == 0; });
bool any_even = std::any_of(v.begin(), v.end(), [](int i){ return i % 2 == 0; });

Lambda Expressions

Lambdas are anonymous functions defined in-place. They are essential for using STL algorithms effectively.

Syntax

[captures](parameters) -> return_type { body }

Captures

  • []: No captures.
  • [=]: Capture all local variables by value (copy).
  • [&]: Capture all local variables by reference.
  • [x, &y]: Capture x by value, y by reference.
1
2
3
4
int factor = 2;
auto multiply = [factor](int n) {
    return n * factor;
};

Mutable Lambdas

By default, value captures are read-only. Use mutable to modify them (this modifies the lambda's copy, not the original).

1
2
3
4
int count = 0;
auto counter = [count]() mutable {
    return ++count;
};

Parallel Algorithms (C++17)

Many algorithms now accept an execution policy to run in parallel. This can significantly speed up operations on large datasets.

#include <execution>
#include <algorithm>
#include <vector>

std::vector<int> large_data(1000000);
// ... fill data ...

// Parallel Sort
std::sort(std::execution::par, large_data.begin(), large_data.end());

// Parallel Transform
std::transform(std::execution::par_unseq, 
               large_data.begin(), large_data.end(), 
               large_data.begin(),
               [](int n) { return n * 2; });

C++20 Ranges

Ranges simplify the usage of algorithms by allowing you to pass the container directly, rather than begin() and end(). They also support piping.

#include <ranges>
#include <algorithm>
#include <vector>

std::vector<int> numbers = {1, 2, 3, 4, 5, 6};

// Filter even numbers and square them
auto results = numbers 
             | std::views::filter([](int n){ return n % 2 == 0; })
             | std::views::transform([](int n){ return n * n; });

```cpp
for (int n : results) {
    std::cout << n << " "; // 4 16 36
}

Projections (C++20)

Ranges algorithms often accept a projection, which is a transformation applied to elements before the algorithm operates on them. This makes sorting by member variable trivial.

1
2
3
4
5
6
7
8
struct User { int id; std::string name; };
std::vector<User> users = {{2, "Bob"}, {1, "Alice"}};

// Sort by ID
std::ranges::sort(users, {}, &User::id);

// Sort by Name length
std::ranges::sort(users, {}, [](const User& u) { return u.name.length(); });

New in C++23 (Ranges)

C++23 fills in the gaps of C++20 ranges.

  • std::ranges::contains: Check if a range contains a value.
  • std::ranges::fold_left: Range-based accumulation.
1
2
3
4
5
6
7
8
std::vector<int> v = {1, 2, 3, 4, 5};

if (std::ranges::contains(v, 3)) { // C++23
    std::cout << "Found 3!";
}

// Sum elements (C++23)
int sum = std::ranges::fold_left(v, 0, std::plus<int>());

Best Practices

  1. Prefer Algorithms over Loops: Algorithms are named after what they do (find, sort, count), making code more readable and less error-prone.
  2. Use Ranges (C++20): They are safer and more composable.
  3. Be Careful with Captures: Capturing pointers or references in lambdas that outlive the scope leads to dangling references.