C++ Ranges Library Cheat Sheet
Views, adaptors, and pipe-composed algorithms from std::ranges (C++20/23) for lazy, composable iteration without manual iterator pairs.
Basic Range Algorithms
std::ranges algorithms take a range directly, no begin()/end() pair needed.
#include <ranges>#include <algorithm>#include <vector>#include <iostream>std::vector<int> v{5, 3, 1, 4, 2};std::ranges::sort(v); // sorts in place, no v.begin(), v.end()auto it = std::ranges::find(v, 3); // returns iteratorbool any = std::ranges::any_of(v, [](int x) { return x > 4; });auto [min_it, max_it] = std::ranges::minmax_element(v);
Lazy Views & Pipe Syntax
Views are lazy, non-owning, composable with the | operator.
#include <ranges>#include <vector>std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};auto result = nums | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; }) | std::views::take(3);for (int x : result) { // 4, 16, 36 -- computed lazily on iteration std::cout << x << ' ';}// Common views: filter, transform, take, drop, reverse, join, split,// take_while, drop_while, iota, enumerate (C++23), zip (C++23)
Materializing & C++23 additions
Convert a view back into a concrete container; ranges::to arrives in C++23.
#include <ranges>#include <vector>auto view = std::views::iota(1, 11) | std::views::filter([](int n) { return n % 3 == 0; });// C++23: ranges::to converts a view into a containerstd::vector<int> multiples_of_3 = view | std::ranges::to<std::vector>();// Pre-C++23: construct explicitlystd::vector<int> manual(view.begin(), view.end());// C++23: views::enumerate and views::zipfor (auto [i, x] : std::views::enumerate(multiples_of_3)) { std::cout << i << ": " << x << '\n';}
Concepts & Constrained Algorithms
Ranges algorithms are constrained by concepts, giving better compile errors.
#include <ranges>#include <concepts>template <std::ranges::input_range R> requires std::equality_comparable<std::ranges::range_value_t<R>>bool contains(R&& r, const std::ranges::range_value_t<R>& value) { return std::ranges::find(r, value) != std::ranges::end(r);}// Projections: transform elements before comparison without a full transform viewstruct Person { std::string name; int age; };std::vector<Person> people{{"Bob", 30}, {"Ann", 25}};std::ranges::sort(people, std::ranges::less{}, &Person::age); // sort by age via projection
Common View Adaptors
The most frequently used entries in std::views.
- views::filter(pred)- keep elements where pred returns true
- views::transform(f)- lazily apply f to each element
- views::take(n) / drop(n)- first n elements / skip first n
- views::reverse- iterate in reverse order
- views::split(delim)- split a range into subranges (C++20, refined in C++23)
- views::join- flatten a range of ranges
- views::iota(start[, end])- generate an infinite or bounded arithmetic sequence
- views::zip(r1, r2, ...)- iterate multiple ranges in lockstep (C++23)
Viewing Associative Containers & Tuples
views::keys, views::values, and views::elements<N> project a single component out of pair/tuple-like elements.
#include <ranges>#include <map>#include <vector>#include <tuple>#include <iostream>std::map<std::string, int> ages{{"Ann", 25}, {"Bob", 30}};for (auto& key : ages | std::views::keys) { // view over just the keys std::cout << key << ' ';}for (auto& val : ages | std::views::values) { // view over just the mapped values std::cout << val << ' ';}std::vector<std::tuple<int, std::string, double>> rows{{1, "a", 1.5}, {2, "b", 2.5}};for (auto& name : rows | std::views::elements<1>) { // pick tuple element by index (0-based) std::cout << name << ' ';}
Windowing: chunk, slide & adjacent (C++23)
C++23 adds views for grouping a range into fixed-size non-overlapping chunks or sliding overlapping windows.
#include <ranges>#include <vector>#include <iostream>std::vector<int> v{1, 2, 3, 4, 5, 6, 7};for (auto chunk : v | std::views::chunk(3)) { // non-overlapping groups of 3 for (int x : chunk) std::cout << x << ' '; std::cout << "| ";} // {1,2,3} | {4,5,6} | {7}for (auto window : v | std::views::slide(3)) { // overlapping windows of size 3 // {1,2,3}, {2,3,4}, {3,4,5}, {4,5,6}, {5,6,7}}for (auto [a, b] : v | std::views::adjacent<2>) { // fixed-size tuples of neighbors std::cout << a + b << ' '; // pairwise sums}// views::pairwise is shorthand for views::adjacent<2>
Dangling Iterators & borrowed_range
ranges algorithms detect when an iterator would outlive a temporary container and refuse to compile instead of returning a dangling iterator.
#include <ranges>#include <vector>auto get_evens() { std::vector<int> v{1, 2, 3, 4}; return v | std::views::filter([](int n) { return n % 2 == 0; }); // DANGER: v is destroyed on return, the returned view now dangles at runtime}auto get_first() { std::vector<int> local{1, 2, 3}; return std::ranges::find(local, 2); // COMPILE ERROR: ranges::find on an rvalue container returns // std::ranges::dangling instead of an iterator, so this fails to compile. // Types satisfying borrowed_range (std::span, ref_view, string_view, ...) // are exempt because they never own the underlying storage.}
Writing a Custom Pipeable Adaptor
range_adaptor_closure (C++23) gives any callable object the | pipe syntax for free, matching the built-in views.
#include <ranges>struct take_evens_fn : std::ranges::range_adaptor_closure<take_evens_fn> { template <std::ranges::viewable_range R> constexpr auto operator()(R&& r) const { return std::forward<R>(r) | std::views::filter([](auto&& x) { return x % 2 == 0; }); }};inline constexpr take_evens_fn take_evens{};// now composes with the standard pipe chain like any built-in view adaptorauto result = std::vector{1, 2, 3, 4, 5} | take_evens | std::views::take(2);
C++23 Ranges Additions
New views and utilities added to std::ranges beyond the original C++20 set.
- views::zip(r1, r2, ...)- iterate multiple ranges in lockstep, yielding tuples
- views::enumerate- yields (index, element) pairs without a manual counter
- views::chunk(n) / slide(n)- fixed-size non-overlapping groups / overlapping windows
- views::adjacent<N> / pairwise- tuples of N consecutive elements; pairwise is adjacent<2>
- views::cartesian_product(r1, r2, ...)- lazily iterate the full cross-product of several ranges
- views::join_with(delim)- flatten a range of ranges, inserting a delimiter between groups
- views::as_rvalue- turns iteration into move semantics, useful for draining into a container
- ranges::to<Container>()- materializes any view into a concrete container, replacing manual begin()/end() construction
Views are lazy and non-owning — never return a view that references a local container by value from a function; the container is destroyed on return and the view becomes a dangling reference, a very easy trap to fall into with pipe chains.