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