Skip to content

Expert · Modern C++: lambdas, C++17 and C++20 essentials · topic 33 of 42 · 30 min

C++20 ranges, views and other C++20 essentials

Explanation

C++20 ranges let algorithms take a whole container (std::ranges::sort(v)) and add views: lazy, composable steps like filter and transform, joined with the | pipe. C++20 also brought concepts, std::span, designated initialisers, the spaceship operator, std::format and modules. Check your compiler's support for each feature.

Why ranges?

Classic STL algorithms take two iterators: std::sort(v.begin(), v.end()). Passing the wrong pair (v.begin(), w.end()) compiles and corrupts memory. A range is anything with a begin and an end, like a container. The C++20 ranges library (<ranges> and the std::ranges:: algorithms) lets you pass the range itself.

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

std::vector<int> v = {5, 2, 8, 1};
std::ranges::sort(v);                                  // 1 2 5 8
auto it = std::ranges::find(v, 8);
bool allPositive = std::ranges::all_of(v, [](int x) { return x > 0; });
Same algorithms, shorter calls, fewer mistakes. ranges algorithms are also constrained with concepts, so misuse gives clearer errors.

Projections

cpp
struct Order { std::string id; int amount; };
std::vector<Order> orders = {{"A1", 499}, {"A2", 120}, {"A3", 999}};
std::ranges::sort(orders, {}, &Order::amount);                 // sort by amount
auto big = std::ranges::max_element(orders, {}, &Order::amount); // order with the largest amount
A projection says "compare by this member". {} means "use the default comparison".

Views: lazy pipelines

cpp
#include <iostream>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> marks = {35, 82, 47, 91, 28, 66};
    auto passedDoubled = marks
        | std::views::filter([](int m) { return m >= 40; })
        | std::views::transform([](int m) { return m * 2; });
    for (int x : passedDoubled) std::cout << x << ' ';   // 164 94 182 132
    std::cout << '\n';

    for (int i : std::views::iota(1, 6)) std::cout << i;   // 12345
    std::cout << '\n';
    for (int x : marks | std::views::reverse | std::views::take(2)) std::cout << x << ' ';   // 66 28
}
A view does no work until you loop over it, and makes no copies. The | pipe chains steps like a Unix shell pipeline.
Think of it like this: Views are like a water purifier with several filters in a line: sediment filter, then carbon filter, then UV. Water flows through each stage only when you open the tap, and no stage stores a tank of water. Opening the tap is the for loop.
Careful: Views refer to the data they come from; they do not own it. Returning a view of a local vector from a function makes it dangle, just like a string_view. If you need a stored result, copy the elements into a container (C++23 has std::ranges::to<std::vector>(); in C++20, use a loop or std::vector(view.begin(), view.end()) for common_range views).

Other C++20 features worth knowing

FeatureWhat it gives you
ConceptsNamed requirements on templates and clearer errors (seen in the templates topic)
std::span<T>A non-owning view of a contiguous array: pass vectors, arrays and C arrays to one function
Designated initialisersPoint p{.x = 1, .y = 2}; names the fields when initialising a struct
Three-way comparison <=>One defaulted operator gives all comparisons
std::formatPython-style formatting: std::format("{:.2f}", x)
contains()map.contains(key) and set.contains(key)
std::jthreadA thread that joins itself in its destructor (RAII)
Modulesimport instead of #include for faster builds; build tool support is still maturing
Coroutinesco_await / co_yield for async code and generators; mostly used through libraries
cpp
#include <span>

double average(std::span<const int> values) {        // works with vector, array, C array
    if (values.empty()) return 0;
    long long sum = 0;
    for (int v : values) sum += v;
    return static_cast<double>(sum) / values.size();
}

std::vector<int> a = {70, 80};
int b[] = {60, 90, 75};
average(a);
average(b);
std::span is to arrays what std::string_view is to strings.
Tip: Compiler support for C++20 is good in GCC 11+, Clang 16+ and Visual Studio 2022, but library features like std::format, ranges and modules arrived at different times. If a header is missing, check the "compiler support" page on cppreference and your compiler version.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

C++20 ranges, views and other C++20 essentials · 1/6

Ranges and views

C plus plus twenty modernised the algorithms with ranges.

Prefer a video by a teacher? Find free videos on “c++20 ranges views filter transform pipe tutorial” on YouTube ↗

In real life: Today's high-value UPI payments

A shop owner wants the IDs of today's UPI payments above Rs 1,000, newest first, for a quick check. A ranges pipeline (filter, then transform to the ID) reads like the requirement and makes no intermediate copies.

cpp
#include <iostream>
#include <ranges>
#include <string>
#include <vector>

struct Payment { std::string id; int rupees; };

int main() {
    std::vector<Payment> today = {{"U101", 250}, {"U102", 1800}, {"U103", 999}, {"U104", 5200}};
    auto bigIds = today
        | std::views::reverse
        | std::views::filter([](const Payment& p) { return p.rupees > 1000; })
        | std::views::transform(&Payment::id);
    for (const auto& id : bigIds) std::cout << id << ' ';   // U104 U102
    std::cout << '\n';
}
transform can take a pointer to a member as its function.

Practice (0/4 done)

Task 1 · Question

Which is the C++20 ranges way to sort vector v?

Task 2 · Question

What does this print? std::vector<int> v = {1, 2, 3, 4, 5, 6}; for (int x : v | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; })) std::cout << x << " ";

Task 3 · Question

When does a view like std::views::filter do its work?

Task 4 · Practice

Given std::vector<Student> (name, marks), use C++20 ranges to: sort by marks descending with a projection, then print the names of the first 3 students who scored at least 60 using views::filter, views::transform and views::take.

Quick quiz

Question 1

1.What does the | operator do between views?

Question 2

2.What is std::span<const int> good for?

Question 3

3.Which is a valid C++20 designated initialiser for struct P { int x; int y; };?

Question 4

4.What is the main risk of returning a view of a local vector?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does the | operator do between views?

Answer: Chains view steps into a pipeline

Range adaptors compose with the pipe operator.

What is std::span<const int> good for?

Answer: Passing any contiguous array (vector, std::array, C array) to a function without copying

span is a non-owning view of contiguous elements.

Which is a valid C++20 designated initialiser for struct P { int x; int y; };?

Answer: P p{.x = 1, .y = 2};

Designators use .name and must follow the declaration order.

What is the main risk of returning a view of a local vector?

Answer: The view dangles because the vector is destroyed

Views reference their source; the source must outlive them.

Which C++ standard introduced optional, variant and string_view?Related

Answer: C++17

All three came in C++17.

From std::optional, std::variant and std::string_view

Why can you not call std::sort on a std::list?Related

Answer: std::sort needs random-access iterators; list iterators are only bidirectional

Use the member function list.sort() instead.

From Iterators: the glue between containers and algorithms

What does a C++20 concept do?Related

Answer: States compile-time requirements on template arguments, giving clearer errors

Concepts constrain templates and improve error messages.

From Templates: code for any type

What is the main advantage of std::vector over a C-style array?Related

Answer: Its size can change at run time and it knows its size

vector grows with push_back and reports size(); it also manages its own memory.

From Arrays, std::array and a first look at std::vector

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Modern C++: lambdas, C++17 and C++20 essentials”. The module exam comes next.
  2. Module 8 exam: Modern C++ · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Exceptions, concurrency and building projects with CMake. 42 topics left in the course before the final exam.

← Previous: std::optional, std::variant and std::string_view