SovranCode
Modern C++ Lesson 35
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ sort and binary_search: Rules and Complexity
NEXT LESSONC++ optional and variant Explained
C++ foundations · Lesson 35 About 48 min with practice

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.

Before you begin

Know vectors, lambdas, algorithms and borrowed views. These complete programs require a compiler and standard library that support C++20 ranges.

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.

A living owner holds one, two, three and four. A filter describes selecting two and four, then a transform describes multiplying them by ten. A reading loop produces twenty and forty; a separate vector stores copied results.
The pipeline is a recipe over a living named owner, not a stored output vector. In these examples the source stays unchanged while the view is used, and a separate vector owns any saved results.
C++ PRACTICE RUNNER

Read a two-step pipeline

The named vector lives through the loop. The pipe applies filter before transform.

#include <iostream>
#include <ranges>
#include <vector>
int main() {
    const std::vector<int> values{1, 2, 3, 4};
    auto result = values
        | std::views::filter([](int value) { return value % 2 == 0; })
        | std::views::transform([](int value) { return value * 10; });
    std::cout << "Result:";
    for (int value : result) std::cout << ' ' << value;
    std::cout << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.
C++ PRACTICE RUNNER

Save the results in a real vector

The loop copies each computed value into saved. saved then owns those integers.

#include <iostream>
#include <ranges>
#include <vector>
int main() {
    const std::vector<int> values{1, 2, 3, 4};
    auto recipe = values
        | std::views::filter([](int value) { return value % 2 == 0; })
        | std::views::transform([](int value) { return value * 10; });
    std::vector<int> saved;
    for (int value : recipe) saved.push_back(value);
    std::cout << "Saved:";
    for (int value : saved) std::cout << ' ' << value;
    std::cout << "\nSource size: " << values.size() << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.

C++ PRACTICE RUNNER

Use a plain loop to check the same rule

This is a readable alternative and a way to verify the pipeline's intended result.

#include <iostream>
#include <vector>
int main() {
    const std::vector<int> values{1, 2, 3, 4};
    std::vector<int> saved;
    for (int value : values) {
        if (value % 2 == 0) saved.push_back(value * 10);
    }
    std::cout << "Result:";
    for (int value : saved) std::cout << ' ' << value;
    std::cout << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.

LOCAL TERMINAL COMMANDS

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.exe
APPLY THE CONCEPT

Independent 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?
  1. Complete validation before constructing the pipeline.
  2. Use auto for the filter view object; the rules can still be read-only.
  3. A range-for loop with push_back stores results without needing C++23 ranges::to.
C++ PRACTICE RUNNER

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.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
#include <ranges>
#include <vector>
int main() {
    int count{};
    if (!readInteger(count) || count < 0 || count > 6) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::vector<int> values;
    for (int i = 0; i < count; ++i) {
        int value{};
        if (!readInteger(value) || value < -50 || value > 50) {
            std::cout << "Invalid input\n"; return 1;
        }
        values.push_back(value);
    }
    int threshold{};
    if (!readInteger(threshold) || threshold < -50 || threshold > 50) {
        std::cout << "Invalid input\n"; return 1;
    }
    auto recipe = values
        | std::views::filter([threshold](int value) { return value > threshold; })
        | std::views::transform([](int value) { return value * 2; });
    std::vector<int> saved;
    for (int value : recipe) saved.push_back(value);
    std::cout << "Selected:";
    for (int value : saved) std::cout << ' ' << value;
    std::cout << "\nCount: " << saved.size() << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

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.

  • C++ draft: range adaptors
  • C++ draft: filter view
  • C++ draft: transform view
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01What does this pipeline store before traversal?
02Which step selects values?
03Why keep the source vector alive?
04Why use push_back instead of ranges::to here?
05What should a beginner do before changing the source?
PREVIOUS LESSONC++ sort and binary_search: Rules and Complexity
NEXT LESSONC++ optional and variant Explained
ON THIS PAGELearning objectivesA pipeline describes a few small stepsFollow one value through the stepsLazy means the result is read when neededKeep the owner and the rules validSelect readings, double them and save the answerIndependent labReviewKnowledge checkReferences
Course overview