C/C++ Arena

Step 5 of 7

Ranges

C++20 ranges make algorithms easier to use and add views: lazy, composable steps that you chain with |, like a Unix pipeline.

#include <algorithm>
#include <iostream>
#include <ranges>
#include <string>
#include <vector>

struct Order {
    std::string item;
    int qty;
};

int main() {
    std::vector<int> v = {5, 2, 9, 1, 6};
    std::ranges::sort(v);
    for (int x : v) std::cout << x << " ";
    std::cout << "\n";

    std::vector<Order> orders = {{"tea", 3}, {"cake", 0}, {"jam", 2}, {"bun", 0}};
    auto shipped = orders
        | std::views::filter([](const Order& o) { return o.qty > 0; })
        | std::views::transform([](const Order& o) { return o.item; });

    std::vector<std::string> names;
    for (const auto& n : shipped) names.push_back(n);
    for (const auto& n : names) std::cout << n << " ";
    std::cout << "(" << names.size() << ")\n";
}
1 2 5 6 9 
tea jam (2)

Lazy means nothing happens yet

Building shipped doesn't filter or copy anything. It's a recipe. The work happens element by element while the for loop pulls values through the pipeline. That's why views are cheap even on huge inputs, and why you usually finish by looping over the view and collecting results, as the example does.

Reading a pipeline

Read it top to bottom: start with orders, keep the ones with quantity above zero, turn each into its item name. The order of steps matters: transform first and you'd lose qty before you could filter on it.

Common mistake

A view refers to the original container. If the container is destroyed or changed while you still use the view, it dangles, just like string_view.

Your turn: write std::vector<std::string> top_fraggers(const std::vector<std::pair<std::string, int>>& players, int min_kills) that returns the names of players with at least min_kills, in their original order, using views::filter and views::transform.

Previous: Concepts Next: std::span