cpp-ranges
C++20 ranges is a standard library feature that treats a sequence as a lightweight view so that filtering, transforming, and sorting can be composed without copying elements.
This article restates the cppreference ranges, constrained-algorithms, filter_view, and transform_view pages as of 2026-09-01; details may differ by standard version and implementation.
How does the C++20 library make sequences composable?
One-line answer: The library extends and generalizes the algorithm and iterator libraries by representing iterable sequences as lightweight views that can be composed into pipelines.
Available through the <ranges> header in C++20, the library turns the usual iterator-based model into a more composable interface. A view is a lightweight object that indirectly represents an iterable sequence rather than storing a second copy of it.
The central model is a [begin, end) iterator-sentinel pair. A container can provide that sequence through its iterators, and algorithms that traditionally accepted iterator pairs also provide overloads accepting one range argument, including std::ranges::sort.
Range adaptors are applied lazily to views, while range algorithms are applied eagerly. Adaptors can be joined with | into a pipeline, and their work unfolds as the resulting view is iterated. Counted, conditionally terminated, and unbounded sequences also fit the abstraction, but they are only mentioned here.
std::views is an alias for std::ranges::views, so std::views::filter is a concise spelling for that adaptor namespace.
What separates a view from a container?
One-line answer: A container manages stored elements and their storage, whereas a typical adaptor-produced view represents an underlying sequence without copying its elements.
Views do not own. As a teaching rule for the usual adaptors, filter_view and transform_view refer to an underlying sequence instead of making another copy of the container’s elements.
The view concept describes the semantic properties needed for a range type to participate in adaptor pipelines. Move construction must be constant-time, and copy construction must also be constant-time when the type is copyable. Assignment and destruction are expected to remain inexpensive as well.
A copyable container such as std::vector generally does not meet those view semantics because copying it copies all of its elements, which cannot be done in constant time. Non-ownership is not a requirement for the concept, however; owning_view is also defined.
For a contiguous non-owning view, see the span article; this explanation stays focused on lightweight adaptor pipelines.
How can filtering and transformation be composed?
One-line answer: Put std::views::filter before std::views::transform to exclude elements that fail a predicate and transform the survivors as iteration requests them.
For suitable expressions e and p, std::views::filter(e, p) is expression-equivalent to std::ranges::filter_view(e, p). The one-argument form std::views::filter(pred) is a range-adaptor closure that can be placed after a pipe. Similarly, std::views::transform(e, f) corresponds to std::ranges::transform_view(e, f), and std::views::transform(fun) supplies the pipeline closure.
#include <iostream>
#include <ranges>
int main()
{
auto const ints = {0, 1, 2, 3, 4, 5};
auto even = [](int i) { return 0 == i % 2; };
auto square = [](int i) { return i * i; };
for (int i : ints | std::views::filter(even) | std::views::transform(square))
std::cout << i << ' ';
}
This is the documented even-number-and-square example; its stated output is 0 4 16.
The predicate and transformation are not run when the pipeline object is assembled. Because the adaptors are lazy, they run as the view is iterated. The same composition can be written functionally as std::views::transform(std::views::filter(ints, even), square).
For a separate check of core C++ concepts, see C++ 기술면접 질문 10가지; that article covers a different scope.
Why pass a range to a constrained algorithm?
One-line answer: The constrained algorithm accepts either an iterator-sentinel pair or one range argument, checks its requirements and projection, and acts on the target immediately.
C++20 supplies constrained versions of most algorithms in the std::ranges namespace. They support both the iterator-sentinel form and the single-range form, so a call site can pass a container without spelling out its begin() and end() pair.
The range overload of std::ranges::sort requires std::ranges::random_access_range and std::sortable. Its default comparator is std::ranges::less, and its default projection is std::identity.
#include <algorithm>
#include <array>
std::array s{5, 7, 4, 2, 8, 6, 1, 9, 0, 3};
std::ranges::sort(s);
This call passes one range argument rather than s.begin(), s.end(). Sorting happens eagerly and immediately changes the target range. The relative order of equivalent elements is not guaranteed; the return value is the target’s past-the-end iterator, and the documented complexity is O(N log N) applications of comp and proj.
A pointer-to-member can be used as the projection to select the field being compared without writing a separate comparator. That eager mutation is distinct from std::views::filter and std::views::transform, whose lazy traversal does not sort or copy the result into a new container by itself.
FAQ
One-line answer: The key distinctions are whether a view composition copies elements, when lazy work is evaluated, and which requirements the immediate sort must satisfy.
Which C++20 facility should I use to work with a sequence without copying its elements? Use view adaptors from the <ranges> header to compose filtering and transformation over the underlying sequence.
Does a view own the container’s elements? Views do not own. Typical filter_view and transform_view objects represent the underlying sequence without copying its elements. Non-ownership is not mandatory for the view concept, and owning_view also exists.
When do filter and transform run? The adaptors are lazy: their predicate and transformation run as the view is iterated, not when the pipeline is constructed.
Why choose std::ranges::sort instead of std::sort? Its range overload accepts the container as one argument, checks random_access_range and sortable, and supports a projection for selecting the compared field. The call sorts the target immediately.
Sources
One-line answer: The definitions and examples are based only on the cited cppreference C++20 pages checked on 2026-09-01.
- C++20 library overview — the library scope, views, lazy adaptors, eager algorithms, pipelines, and the
std::viewsalias. - view concept — pipeline-oriented semantics, copy and move properties, and the ownership nuance.
filter_view— exclusion by predicate and the adaptor-closure form.transform_view— element-wise transformation and the adaptor-closure form.- constrained algorithms — iterator-sentinel pairs, single-range arguments, and projections.
std::ranges::sort— overload requirements, default comparator and projection, return value, sorting behavior, and complexity.