C++20 Ranges: Views, filter and transform
Learn C++20 ranges, filter and transform in simple English: read a pipeline, understand lazy work, keep owners alive and save results in a vector.
What you will learn
- Read a filter and transform pipeline from left to right.
- Explain lazy work as work performed while reading.
- Keep the source owner alive and unchanged during traversal.
- Save computed values in a separate owning vector.
- Compare a pipeline with an ordinary loop.
A pipeline describes a few small steps
Suppose you have the values one, two, three and four. You want only the even numbers, with each selected number multiplied by ten. An ordinary loop can do that. C++20 ranges also let you describe the same steps as a pipeline: start with the values, filter the even ones, then transform each selected value. The pipe symbol connects the steps. It is not the shell pipe used between terminal commands.
A range is a sequence you can traverse from a beginning to an ending. A vector is one example. A view is a range type designed to be cheap to move; the views in this lesson describe work over an existing named vector. They do not make an independent copy of all its elements. Keep that distinction clear: a useful description of how to read data is not automatically a new owned data set.
filter keeps the elements for which its rule returns true during traversal. transform computes a result from each element it receives. We use read-only rules and a named source that stays alive. Some other range constructions can own their source, so do not turn this lesson's borrowing example into a claim that every view always borrows. Start with one clear lifetime relationship before exploring the wider library.

Read a two-step pipeline
The named vector lives through the loop. The pipe applies filter before transform.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Result: 20 40
After editing inputs, calculate a new prediction; this reference describes only the starter.
Follow one value through the steps
Read the first program from left to right. One reaches the even-number test and fails. Two passes, so the next rule turns it into twenty. Three fails the test. Four passes and becomes forty. The output loop visits only the values produced by those steps. Neither rule writes into the original vector. The source still contains one, two, three and four after the loop ends.
The syntax starts with a named vector, then a pipe and filter, then another pipe and transform. Each lambda has one small job. The first answers a true-or-false question. The second returns a computed integer. Separating the jobs helps you check the result by hand. If a pipeline is hard to explain aloud, shorten it or write a normal loop. Ranges are an option for clarity, not a requirement to make every loop shorter.
We declare the view object with auto, not const auto. In C++20, some view combinations, including filter views, do not provide traversal through a const view object. This does not mean the source must be writable. The source vector in the example is const, and the rules only read its elements. Const on the owner and const on the recipe object answer different questions, just as const on a span handle differs from const on its elements.
- 1 fails the even test, so it produces no output.
- 2 passes and becomes 20.
- 3 fails the test, so it produces no output.
- 4 passes and becomes 40; the owner remains unchanged.
Save the results in a real vector
The loop copies each computed value into saved. saved then owns those integers.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Saved: 20 40
Source size: 4
After editing inputs, calculate a new prediction; this reference describes only the starter.
Lazy means the result is read when needed
Creating this pipeline does not eagerly fill a new vector with all the answers. The view describes how traversal obtains them. The work happens as iteration asks for elements. This is called lazy evaluation. Do not use a counter inside a predicate to guess an exact number of calls. Implementations can cache some traversal information, and later reads may perform work again. Keep predicates and transformations focused on their input rather than on side effects.
The second example makes ownership explicit. recipe still describes access to values. The loop reads the recipe and pushes each produced integer into saved. saved is now a separate owning vector. It can be passed around as ordinary data without keeping that view alive. This is sometimes called materializing a result; in simple words, it means actually storing the produced values instead of keeping only the recipe.
This course uses C++20. You may see examples online that finish with ranges::to, which is a later standard-library facility. We deliberately use a loop with push_back so the lesson does not silently require C++23. If your compiler accepts the language flag but cannot find the ranges features, check the standard-library support as well. A language mode does not install a newer library or make every feature available on an older toolchain.
Use a plain loop to check the same rule
This is a readable alternative and a way to verify the pipeline's intended result.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Result: 20 40
After editing inputs, calculate a new prediction; this reference describes only the starter.
Keep the owner and the rules valid
Our view reads a named vector that remains alive until after traversal. Returning this view from a function that owns a local vector would leave the caller with access to destroyed data. Returning saved as a vector value is a simple repair when the result must survive the function. Do not use a nonempty view or a non-null address as proof that its source is still alive. The owner relationship is what matters.
Do not grow, erase or replace the source while traversing this pipeline. Structural changes can invalidate iterators or assumptions used by the view. Even element changes can interact with cached filtering positions in ways beginners do not expect. For this lesson, finish input collection, create the view, read it with an unchanged source, and save results separately. If you need another view after changing the source, create it again after the change rather than relying on stale traversal state.
Captures also have lifetimes. Our lab captures the threshold by value, giving the rule a small owned integer copy. A reference capture would need its original variable to stay alive and suitable for every later traversal. Avoid returning a view whose lambda borrows a function-local threshold. A repair should address both dependencies: the source data and the state used by the rules. Fixing only the vector lifetime may still leave a dangling capture.
Select readings, double them and save the answer
Read a count from zero through six, that many readings from minus fifty through fifty, and a threshold in that same range. Validate each complete line before printing anything. Build a filter for values strictly greater than the threshold, then a transform that doubles the selected values. Capture the threshold by value and keep the vector unchanged during traversal. Store the results in another vector using push_back.
Print Selected followed by the stored results and Count followed by their number. A value equal to the threshold is excluded. Repeated passing values remain repeated and arrival order stays the same. With count zero, the result is empty, but a valid threshold line is still required. Test none passing, all passing, negative passing values, exact thresholds and malformed input. Explain the recipe and the owned result as two separate objects before moving on.
Run the same program on your computer
Save one complete example as main.cpp. Choose your installed compiler and run only after a successful build. These are terminal commands, not C++ source. The in-page Run button needs the configured compiler sandbox.
macOS / Linux:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or use g++ with the same options.
./lesson
Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exeIndependent lab
Build a C++20 filter/transform view, then save its values in a separate owning vector.
Acceptance checklist
- Read count 0..6, readings -50..50, then threshold -50..50 on complete lines.
- Reject bad required input as Invalid input with status 1 before reporting.
- Filter with value > threshold and capture threshold by value.
- Transform each passing value to value * 2; keep the source alive and unchanged.
- Store results in a vector and print Selected and Count, including empty results.
Need a hint?
- Complete validation before constructing the pipeline.
- Use auto for the filter view object; the rules can still be read-only.
- A range-for loop with push_back stores results without needing C++23 ranges::to.
Write and run your lab
Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.
Predict the output, edit the source, then select Run code.
Your lab evidence and notes
Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.
Loading local notes…
Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.
Reveal the explained lab solution
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
Expected output
Selected: 10 10
Count: 2
The filter keeps only values strictly above the copied threshold; transform doubles those values without changing the source.
The vector values and the lambda's captured threshold remain valid while the recipe is traversed. No source mutation invalidates the traversal.
saved owns the produced integers. It is not another borrowing recipe, and the explicit loop works in the course's C++20 baseline.
Review before moving on
Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.
- Read a filter and transform pipeline from left to right.
- Explain lazy work as work performed while reading.
- Keep the source owner alive and unchanged during traversal.
- Save computed values in a separate owning vector.
- Compare a pipeline with an ordinary loop.
References and further reading
Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.