Skip to Content
Course content

242: Range Views: filter, transform, take

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By the time you hit C++20, you've probably spent a lot of time writing loops that look like this: a for loop, an if check inside to skip certain elements, and then some logic to modify the value before pushing it into a new vector. It works, but it's boilerplate-heavy. Range views change that by letting you treat your data like a pipeline.

Why use these views instead of just writing a standard loop?

I get this question a lot. If you can just write a for loop, why learn a whole new syntax? The answer is composability. When you use views, you stop telling the computer how to iterate and start telling it what you want to see.

Imagine you have a list of Player objects in a game, and you only want the names of the players who have a level higher than 50. In the old days, you'd create a temporary std::vector<std::string>, loop through the players, check the level, and push_back the name. With views, you just describe the transformation. It's cleaner, it's more declarative, and—as we'll get to in a moment—it's often faster because it doesn't allocate memory for those temporary containers.

How do I actually chain filter, transform, and take together?

The "magic" here is the pipe operator (|). It works exactly like a Unix shell pipeline. You take your source range, pipe it into a filter, pipe that into a transformation, and so on.

Here is a real-world snippet. Let's say we have a list of game characters and we want the names of the first three "Legendary" characters we find:

#include <iostream>
#include <vector>
#include <string>
#include <ranges> // Essential for views

struct Character {
    std::string name;
    std::string rarity;
    int level;
};

int main() {
    std::vector<Character> roster = {
        {"Arthur", "Common", 10},
        {"Morgana", "Legendary", 85},
        {"Lancelot", "Epic", 60},
        {"Guinevere", "Legendary", 90},
        {"Gawain", "Common", 12},
        {"Percival", "Legendary", 77},
        {"Bors", "Legendary", 82}
    };

    auto top_legendaries = roster 
        | std::views::filter([](const auto& c) { return c.rarity == "Legendary"; })
        | std::views::transform([](const auto& c) { return c.name; })
        | std::views::take(3);

    for (const std::string& name : top_legendaries) {
        std::cout < name < " "; // Outputs: Morgana Guinevere Percival
    }
}

Notice that top_legendaries isn't a vector. It's a view. It's just a set of instructions on how to get the data.

Is this actually efficient, or is it creating copies under the hood?

This is the most important part to understand: Range views are lazy.

When you define top_legendaries in the code above, C++ does absolutely zero filtering and zero transforming. It doesn't even look at the first element of the vector. It simply creates a small object that knows how to perform those operations when you eventually start iterating over it.

The work happens inside the for loop. The loop asks the view for the first element; the view looks at the vector, skips "Arthur" (because of the filter), finds "Morgana", transforms her into a string, and hands it back. This means if you have a million elements but you only take(3), the program only ever processes enough of the list to find those three. You've avoided allocating a temporary vector and you've avoided iterating over the remaining 999,997 elements. It's an incredible win for performance.




📋 Practical Task

Exercise: Processing a Space RPG Inventory

You are building a system for a Space RPG. You have a std::vector of Item objects. Each Item has a name, a weight, and a category (e.g., "Weapon", "Consumable", "Quest").

Your Task: Write a program that uses C++20 range views to:

  1. Filter the inventory to keep only items in the "Weapon" category.
  2. Filter out any weapons that weigh more than 10.0 units (too heavy to carry).
  3. Transform the remaining items to return only their names in uppercase (you can use std::toupper or a simple loop for the string conversion).
  4. Take only the first 2 weapons that meet these criteria.

Print the resulting names to the console. Ensure you use the pipe operator (|) to chain these operations together.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.