Ranges and Views

C++20's ranges library modernises the algorithms: you pass whole containers instead of iterator pairs, and views let you build lazy pipelines — filter, transform, take — with the | operator, without creating intermediate containers.

Range algorithms

Every classic algorithm has a std::ranges:: version that takes the container itself. They also accept a projection — a member or function to apply before comparing:

C++
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

struct Employee {
    std::string name;
    int age;
};

int main() {
    std::vector<int> v{5, 2, 9, 1};
    std::ranges::sort(v);
    std::cout << std::ranges::max(v) << ' ' << std::ranges::count(v, 9) << '\n';

    std::vector<Employee> staff{{"Ana", 31}, {"Ben", 27}, {"Chloe", 35}};
    std::ranges::sort(staff, {}, &Employee::age);            // sort by age (projection)
    for (const auto& e : staff) std::cout << e.name << ' ';
    std::cout << '\n';

    auto it = std::ranges::find(staff, "Chloe", &Employee::name);
    std::cout << it->name << " is " << it->age << '\n';
}
Output
9 1
Ben Ana Chloe
Chloe is 35

The {} means "use the default comparison"; &Employee::age tells the algorithm to compare ages.

Views and pipelines

A view is a lightweight, lazy adaptor over a range. Chaining views with | reads left to right like a recipe:

C++
#include <iostream>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto even_squares = numbers
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; });

    for (int x : even_squares) std::cout << x << ' ';
    std::cout << '\n';
}
Output
4 16 36 64 100

Nothing is computed when even_squares is created. Each value is produced on demand as the loop asks for it, and no temporary vectors are made.

Useful views

ViewProduces
views::filter(pred)Elements for which pred is true
views::transform(f)f(element) for each element
views::take(n), views::drop(n)The first n / all but the first n
views::take_while(pred), drop_whileUntil / after a condition fails
views::reverseReverse order
views::iota(a, b)The integers a, a+1, …, b−1 (or forever with one argument)
views::keys, views::valuesThe first / second parts of pairs (e.g. map entries)
views::split(sep)Sub-ranges between separators
C++
#include <iostream>
#include <map>
#include <ranges>
#include <string>

int main() {
    for (int n : std::views::iota(1) | std::views::filter([](int n) { return n % 7 == 0; })
                                     | std::views::take(5)) {
        std::cout << n << ' ';
    }
    std::cout << '\n';

    std::map<std::string, int> stock{{"apples", 30}, {"pears", 0}, {"plums", 12}};
    for (const auto& name : stock | std::views::keys) std::cout << name << ' ';
    std::cout << '\n';

    for (int v : stock | std::views::values | std::views::reverse) std::cout << v << ' ';
    std::cout << '\n';
}
Output
7 14 21 28 35
apples pears plums
12 0 30

views::iota(1) is an infinite sequence — laziness makes that fine, because take(5) stops it.

Views refer to their source

A view doesn't own data; it looks at the underlying container. Changing the container changes what the view shows, and a view must not outlive its container. To keep the results, copy them into a container:

C++
#include <iostream>
#include <ranges>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> words{"apple", "fig", "banana", "kiwi"};
    auto long_words = words | std::views::filter([](const std::string& w) { return w.size() > 4; });

    std::vector<std::string> kept(long_words.begin(), long_words.end());
    for (const auto& w : kept) std::cout << w << ' ';
    std::cout << '\n';
}
Output
apple banana

C++23 adds std::ranges::to<std::vector>() to do this conversion at the end of a pipeline (lesson 50).

Ranges vs. loops

A pipeline is great when it reads like a description of the result. For complicated logic with several conditions and side effects, a plain loop is often clearer. Use whichever communicates better — both compile to similarly efficient code.

Try it yourself

From a list of temperatures, print the first three readings above 20 degrees, converted to Fahrenheit.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
69.8 74.3 77