ranges
Introduced in C++20, std::ranges is a revamped namespace of STL algorithms that allows safer, more concise code. Using the pipe operator | for view composition, you can declaratively chain multiple transformations and filter operations. Unlike traditional algorithms that require iterator pairs, std::ranges algorithms accept containers directly, eliminating iterator mismatch bugs at the source.
Syntax
// ========================================
// std::ranges basics
// ========================================
#include <ranges>
#include <algorithm>
#include <vector>
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// ---- Traditional std algorithm (requires iterator pair) ----
std::sort(v.begin(), v.end());
// ---- std::ranges algorithm (accepts container directly) ----
std::ranges::sort(v);
// ---- Views: chain transformations with the pipe operator ----
// Multiple views can be composed with |. Lazy evaluation keeps it efficient.
auto result = v
| std::views::filter([](int x) { return x % 2 == 0; }) // keep even numbers
| std::views::transform([](int x) { return x * 10; }); // multiply by 10
// ---- Commonly used views ----
std::views::filter(pred) // passes only elements matching the predicate
std::views::transform(func) // maps each element through a function
std::views::take(n) // takes only the first n elements
std::views::drop(n) // skips the first n elements
std::views::reverse // reverses the order
std::views::iota(start, end) // generates a sequence of integers (no container needed)
Key Views and Algorithms
| Name | Description |
|---|---|
| std::ranges::sort | Sorts a container directly. No iterator pair needed. |
| std::ranges::find | Searches a container for a value and returns an iterator. |
| std::ranges::find_if | Returns the first element satisfying a predicate. |
| std::ranges::count_if | Returns the count of elements satisfying a predicate. |
| std::ranges::for_each | Applies a function to each element. |
| std::ranges::copy | Copies a range to another container. |
| std::views::filter | Creates a view that passes only elements for which the predicate is true. |
| std::views::transform | Creates a view that maps each element through a transformation function. |
| std::views::take(n) | Creates a view of only the first n elements. |
| std::views::drop(n) | Creates a view that skips the first n elements. |
| std::views::reverse | Creates a view with elements in reverse order. |
| std::views::iota(a, b) | Generates a view of integers from a to b-1. No container required. |
| std::views::keys / values | Creates a view of keys or values from map-like pairs. |
| std::ranges::to<T>() | Collects a view into container T (C++23, available from gcc13+). |
Sample Code
kof_ranges.cpp
// ========================================
// kof_ranges.cpp
// Demonstrates std::ranges with KOF
// (The King of Fighters) character data:
// filtering, transforming, and sorting
// ========================================
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <ranges>
// ========================================
// Fighter struct
// ========================================
struct Fighter {
std::string name;
std::string team;
int power;
bool isMain;
};
int main() {
std::vector<Fighter> roster = {
{"Kusanagi Kyo", "Kusanagi Team", 9200, true},
{"Yagami Iori", "Yagami Team", 9500, true},
{"Terry Bogard", "Bogard Team", 8800, true},
{"Shiranui Mai", "Bogard Team", 8200, false},
{"King", "Art of Fighting Team", 8100, false},
{"Billy Kane", "Yagami Team", 7500, false},
};
// ========================================
// 1. std::ranges::sort — sort by power descending
// Pass the container directly (no iterator pair needed)
// Third arg is comparator, fourth is projection
// ========================================
std::cout << "=== Sorted by power (descending) ===" << std::endl;
std::vector<Fighter> sorted = roster;
std::ranges::sort(sorted, std::greater<int>{}, &Fighter::power);
for (const auto& f : sorted) {
std::cout << " " << f.name << " power: " << f.power << std::endl;
}
// ========================================
// 2. views::filter — keep only main characters
// ========================================
std::cout << std::endl << "=== Main characters only ===" << std::endl;
auto mainFighters = roster
| std::views::filter([](const Fighter& f) { return f.isMain; });
for (const auto& f : mainFighters) {
std::cout << " " << f.name << " (" << f.team << ")" << std::endl;
}
// ========================================
// 3. views::transform — extract names
// ========================================
std::cout << std::endl << "=== All character names ===" << std::endl;
auto names = roster
| std::views::transform([](const Fighter& f) { return f.name; });
for (const auto& name : names) {
std::cout << " " << name << std::endl;
}
// ========================================
// 4. Composed pipeline
// Filter Bogard Team with power >= 8100
// then transform to a label string
// ========================================
std::cout << std::endl << "=== Bogard Team, power >= 8100 ===" << std::endl;
auto elite = roster
| std::views::filter([](const Fighter& f) {
return f.team == "Bogard Team" && f.power >= 8100;
})
| std::views::transform([](const Fighter& f) {
return f.name + " [" + std::to_string(f.power) + "]";
});
for (const auto& label : elite) {
std::cout << " " << label << std::endl;
}
// ========================================
// 5. views::iota — generate a sequence without a container
// ========================================
std::cout << std::endl << "=== Round numbers (odd rounds 1-5) ===" << std::endl;
auto oddRounds = std::views::iota(1, 6)
| std::views::filter([](int n) { return n % 2 != 0; });
for (int r : oddRounds) {
std::cout << " ROUND " << r << std::endl;
}
// ========================================
// 6. std::ranges::count_if — aggregate with a condition
// ========================================
std::cout << std::endl << "=== Characters with power >= 8500 ===" << std::endl;
auto topCount = std::ranges::count_if(roster, [](const Fighter& f) {
return f.power >= 8500;
});
std::cout << " " << topCount << " fighter(s)" << std::endl;
// ========================================
// 7. std::ranges::find_if — search
// ========================================
std::cout << std::endl << "=== First member of Yagami Team ===" << std::endl;
auto it = std::ranges::find_if(roster, [](const Fighter& f) {
return f.team == "Yagami Team";
});
if (it != roster.end()) {
std::cout << " Found: " << it->name
<< " power: " << it->power << std::endl;
}
return 0;
}
g++ -std=c++20 kof_ranges.cpp -o kof_ranges && ./kof_ranges === Sorted by power (descending) === Yagami Iori power: 9500 Kusanagi Kyo power: 9200 Terry Bogard power: 8800 Shiranui Mai power: 8200 King power: 8100 Billy Kane power: 7500 === Main characters only === Kusanagi Kyo (Kusanagi Team) Yagami Iori (Yagami Team) Terry Bogard (Bogard Team) === All character names === Kusanagi Kyo Yagami Iori Terry Bogard Shiranui Mai King Billy Kane === Bogard Team, power >= 8100 === Terry Bogard [8800] Shiranui Mai [8200] === Round numbers (odd rounds 1-5) === ROUND 1 ROUND 3 ROUND 5 === Characters with power >= 8500 === 2 fighter(s) === First member of Yagami Team === Found: Yagami Iori power: 9500
Common mistake 1: Reusing a view after modifying the underlying container
Views (results of std::views::filter etc.) are lazy evaluation objects, not containers. Modifying the underlying container after creating a view may lead to undefined behavior. To capture a fixed result, copy the view into a container.
// NG:
std::vector<int> v = {9500, 9200, 8800};
auto view = v | std::views::filter([](int x) { return x > 9000; });
v.push_back(9100); // modifying the container may invalidate the view
for (int x : view) { /* dangerous */ }
OK: Copy the view into a container first if you need a stable result.
// OK:
std::vector<int> v = {9500, 9200, 8800};
auto view = v | std::views::filter([](int x) { return x > 9000; });
std::vector<int> result(view.begin(), view.end()); // copy into a container
// you can modify v after this without affecting result
Common mistake 2: Confusing std::sort with std::ranges::sort
The traditional std::sort takes an iterator pair, while std::ranges::sort accepts a container directly. Mixing them causes a compile error. The projection argument is also exclusive to std::ranges::sort.
// NG:
struct Fighter { std::string name; int power; };
std::vector<Fighter> roster = {{"Yagami Iori", 9500}, {"Kusanagi Kyo", 9200}};
std::sort(roster, std::greater<int>{}, &Fighter::power); // std::sort does not accept a projection
OK: Use std::ranges::sort for projections.
// OK:
std::ranges::sort(roster, std::greater<int>{}, &Fighter::power); // projection works here
Overview
std::ranges is the C++20 namespace that redesigns the traditional STL algorithms with direct container support and concept constraints. The biggest benefits are improved readability from dropping iterator pairs and the ability to compose views via the pipe operator |. Views such as std::views::filter and std::views::transform use lazy evaluation: building the pipeline does not process any data. Computation occurs only when elements are actually read, so even large collections are handled without creating intermediate arrays. The projection argument lets you write std::ranges::sort(v, cmp, &Struct::member) to sort directly by a member variable, eliminating the need for a verbose lambda. std::views::iota generates integer sequences without a container, making it handy for loop indices. Include <ranges> and compile with the -std=c++20 flag. See also STL algorithms and lambda expressions.
If you find any errors or copyright issues, please contact us.