Last updated:2026-10-12
C++20 Ranges Library¶
ranges is one of the major features of C++20, and "views" are an important part of it. Before C++20, the standard library's algorithms were implemented in terms of iterators, for example: std::sort.
Iterators + algorithms can pull off some complex operations — for example, sorting in reverse:
But this comes with some problems:
- It is easy to mix two incompatible iterators.
- Algorithms compose too weakly, and intermediate variables have to be stored
For example: say we have a student information system and we want to compute the total GPA of students aged 21 to 25 with GPA >= 3.5, i.e. the sum over the students satisfying the preceding conditions. Before C++17, we could write pseudo-code like this:
std::vector<Student> students
std::vector<Student> selected;
std::copy_if(students.begin(), students.end(), std::back_inserter(selected),
[](const Student& student) {
return student.age >= 21 && student.age <= 25 && student.gpa >= 3.5;
});
double s = std::accumulate(
selected.begin(), selected.end(), 0.0,
[](double sum, const Student& student) { return sum + student.gpa; });
Notice that filtering the students who satisfy the conditions requires copying them into selected, and only then summing over it.
C++20 introduced a more concise and efficient style: by using ranges and the pipe operator | to chain multiple operations, the sum can be computed directly without intermediate variables, for example:
double s = 0.0;
for (const auto& student :
students | std::views::filter([](const auto& s) {
return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
}) | std::views::transform([](const auto& s) { return s.gpa; })) {
s += student;
}
You can see that multiple algorithms now connect seamlessly, cutting the memory allocations for intermediate temporaries, and readability improves greatly, with the pipe | chaining the different operations together.
Now let's explore C++20 ranges together.
1. Basic Concepts¶
1. range
A range is an abstraction representing a sequence. It can be any container that has iterators, or any object that defines begin() and end() functions. std::vector, std::list, and so on are all examples of ranges. Arrays can also be treated as ranges.
2. view
A view is read-only access to a range. It is a form of lazy evaluation — computation happens only when needed, which means it does not actually store data. For example, std::views::filter and std::views::transform are typical examples of views. They let us filter and transform a range without actually creating a new container.
3. algorithm
Algorithms are functions that operate on ranges or views; for example, std::sort and std::find are both algorithms.
4. |
The pipe operator | can chain views and algorithms together, feeding the result on the left as the input on the right. It makes the code clearer and more concise. For example, students | std::views::filter(...) passes the students range to std::views::filter for filtering, and then passes the result on to subsequent operations.
Taking the student computation above as an example: in this example we feed the students range through | as the input of the filter view, then feed that result as the input of the transform view, and finally get back a range; we loop over that range and sum it up with an accumulation to get the result.
double s = 0.0;
for (const auto& student :
students | std::views::filter([](const auto& s) {
return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
}) | std::views::transform([](const auto& s) { return s.gpa; })) {
s += student;
}
As you can see, using ranges has the following benefits:
- Better code readability
- Lazy evaluation: Ranges introduce the concept of views, allowing lazy evaluation — results are computed only when needed.
For example, computation happens only at *v.begin().
- Fewer errors: The design of Ranges helps reduce errors that are easy to make with traditional iterator operations, such as using incompatible iterators.
- And so on.
The ranges library introduces different concepts to describe different kinds of ranges. These concepts help you better understand and constrain the properties of ranges in generic programming. Here are some commonly used range concepts:
| Concept | Description | Container examples |
|---|---|---|
std::ranges::input_range |
Can be iterated at least once from begin to end | std::forward_list, std::list, std::deque, std::array, std::vector |
std::ranges::forward_range |
Can be iterated multiple times from begin to end | std::forward_list, std::list, std::deque, std::array, std::vector |
std::ranges::bidirectional_range |
Iterators can also move backwards with -- |
std::list, std::deque, std::array, std::vector |
std::ranges::random_access_range |
You can jump to an element in constant time [] |
std::deque, std::array, std::vector |
std::ranges::contiguous_range |
Elements are always stored contiguously in memory | std::array, std::vector |
2. Usage¶
Using this feature is fairly simple: include the header and use the interfaces.
For example: filtering the even numbers out of a bunch of numbers.
#include <ranges>
auto evenNumbers = numbers | std::views::filter([](int x) { return x % 2 == 0; });
Compiling: just specify -std.
For compiler support, see the list below:
gcc >= 10, clang >= 13 (partial), >= 15 fully supported.
Related Topics¶
- Getting Started with C++20 Coroutines | C++那些事
- C++20 channel in Practice | C++那些事
- C++20 Concepts | C++那些事
- C++20 constexpr and the Comma Expression | C++那些事
- C++20 Byte Order (endian) | C++那些事
- C++20 Lambda Expressions (Part 1) | C++那些事
- C++20 Modules | C++那些事
- C++20 std::span Design and Pitfalls | C++那些事
- A Guide to C++20 New Features | C++那些事
- C++20 [[likely]] and [[unlikely]] | C++那些事