C++ Views and Pipelines
A view describes a selection or a change over an existing range. It holds no copy of the values and does its work only when the values are read.
A filter view leaves the source alone
The filter view keeps only the even numbers. The source vector prints the same before and after, because the view never changes it.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
std::cout << "Source:";
for (int n : nums) {
std::cout << " " << n;
}
std::cout << std::endl;
auto evens = nums | std::views::filter([](int n) {
return n % 2 == 0;
});
std::cout << "Evens:";
for (int n : evens) {
std::cout << " " << n;
}
std::cout << std::endl;
std::cout << "Source:";
for (int n : nums) {
std::cout << " " << n;
}
std::cout << std::endl;
return 0;
}Chaining a filter and a transform
The pipeline filters first and transforms second. Each even number is squared as it is read.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
auto squaredEvens = nums | std::views::filter([](int n) {
return n % 2 == 0;
}) | std::views::transform([](int n) {
return n * n;
});
std::cout << "Squared evens:";
for (int n : squaredEvens) {
std::cout << " " << n;
}
std::cout << std::endl;
return 0;
}Slicing with take and drop
take keeps the first values of a pipeline. drop skips values at the start and keeps the rest.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> nums = {10, 20, 30, 40, 50, 60};
auto firstThree = nums | std::views::take(3);
std::cout << "First:";
for (int n : firstThree) {
std::cout << " " << n;
}
std::cout << std::endl;
auto lastTwo = nums | std::views::drop(4);
std::cout << "Last:";
for (int n : lastTwo) {
std::cout << " " << n;
}
std::cout << std::endl;
return 0;
}Work happens only on demand
The counter rises each time the transform runs. Only two values are taken, so the transform runs only twice.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
int counter = 0;
auto timesTen = [&counter](int n) {
counter++;
return n * 10;
};
auto view = nums | std::views::transform(timesTen) | std::views::take(2);
std::cout << "Values:";
for (int v : view) {
std::cout << " " << v;
}
std::cout << std::endl;
std::cout << "Runs: " << counter << std::endl;
return 0;
}Example
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> prices = {150, 80, 220, 95, 300, 130};
auto withTax = prices | std::views::filter([](int price) {
return price >= 100;
}) | std::views::transform([](int price) {
return price + price / 10;
}) | std::views::take(3);
std::cout << "With tax:";
for (int price : withTax) {
std::cout << " " << price;
}
std::cout << std::endl;
return 0;
}Key points
- A view does not copy or change the values it reads.
- Views join with the | symbol to form a pipeline.
- Use std::views::filter to keep the values that pass a test.
- Use std::views::transform to change each value as it is read.
- Use std::views::take and std::views::drop to keep or skip a counted number of values.
In this example, the prices are whole cents. The filter keeps the prices of at least 100 cents. The transform adds a tenth of each price to itself. The take view keeps the first three results, and the loop prints them.
Note
A view does its work lazily, one value at a time, when something reads it.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A program builds a transform view over ten values and takes two of them. How many times does the transform run?