Ranges make data pipelines shorter and safer than iterator pairs, and views avoid building temporary containers.
C++20's <ranges> brings two big improvements.
Range algorithms. Everything in std::ranges:: accepts a whole container: std::ranges::sort(v) instead of std::sort(v.begin(), v.end()). They're constrained with concepts, so misuse produces readable errors, and they accept projections, a function applied to each element before comparing. std::ranges::sort(staff, {}, &Employee::age) sorts by age with no lambda; std::ranges::find(staff, "Mo", &Employee::name) finds by name.
Views. A view is a lightweight, lazy range adaptor that doesn't own elements and computes them on demand. Compose them with |:
views::filter(pred),views::transform(f),views::take(n),views::drop(n),views::reverseviews::iota(a, b)generates numbers;views::iota(1)counts forever (fine when combined withtake)views::keys/views::valueson maps,views::split,views::join
Nothing runs until you iterate, and each element flows through the whole pipeline individually, with no intermediate vectors. C++23 adds views::zip, views::enumerate, views::chunk, views::slide, and std::ranges::to<std::vector>() to collect a view into a container.
Things to watch:
- Views refer to their underlying data. A view over a container must not outlive it, just like an iterator.
- Some views (such as
filter) cache theirbegin()and must be non-const to iterate; pass views to functions by value or forwarding reference rather thanconst&. - Range algorithms called on a temporary container return
std::ranges::danglinginstead of an iterator, so the compiler stops you from using a dangling result.
Example
#include <algorithm>
#include <iostream>
#include <map>
#include <ranges>
#include <string>
#include <vector>
struct Employee { std::string name; int age; };
int main() {
std::vector<Employee> staff{{"Zoe", 41}, {"Al", 29}, {"Mo", 35}};
std::ranges::sort(staff, {}, &Employee::age); // whole range + projection
std::cout << staff.front().name << '\n'; // Al
namespace views = std::views;
auto squares_of_odds = views::iota(1) // 1, 2, 3, ... lazily
| views::filter([](int n) { return n % 2 == 1; })
| views::transform([](int n) { return n * n; })
| views::take(4);
for (int x : squares_of_odds) std::cout << x << ' '; // 1 9 25 49
std::cout << '\n';
std::map<std::string, int> stock{{"apple", 3}, {"kiwi", 0}};
for (const auto& name : stock | views::keys) std::cout << name << ' '; // apple kiwi
std::cout << '\n';
auto it = std::ranges::find(staff, "Mo", &Employee::name); // projection again
std::cout << it->age << '\n'; // 35
// C++23: auto v = squares_of_odds | std::ranges::to<std::vector>();
}Remember
std::ranges::algorithms take whole ranges and support projections- Views are lazy and non-owning; compose them with
| - A view must not outlive the data it looks at
Watch out for
- Returning a view over a local container from a function — the view dangles once the container is destroyed.