๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

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?