Advanced Ranges and Views

Advanced Ranges and Views

Build readable lazy pipelines and understand when to keep views versus materializing results.

Advanced Ranges and Views

Why ranges matter

Ranges let you express data flow directly: filter, transform, slice, then consume. This often reads closer to the problem than manual iterator code.

auto ids = users
    | std::views::filter([](const User& user) { return user.active; })
    | std::views::transform([](const User& user) { return user.id; });

Lazy vs materialized results

The pipeline above is lazy. It does not create a new container automatically.

That is useful when:

Materialize into a container when you need stable ownership or repeated traversal.

Projections simplify algorithms

std::ranges::sort(users, std::greater<>{}, &User::score);

This sorts by score without writing a custom comparator body.

A complete example

std::vector<std::string> top_names(const std::vector<User>& users) {
    auto view = users
        | std::views::filter([](const User& user) { return user.score >= 90; })
        | std::views::transform([](const User& user) { return user.name; });

    return {view.begin(), view.end()};
}

Larger worked pipeline

struct Event {
    std::string type;
    int duration_ms{};
    bool success{};
};

std::vector<std::string> slow_successes(const std::vector<Event>& events) {
    auto view = events
        | std::views::filter([](const Event& event) {
            return event.success && event.duration_ms > 100;
        })
        | std::views::transform([](const Event& event) {
            return std::format("{}:{}ms", event.type, event.duration_ms);
        });

    return {view.begin(), view.end()};
}

This is the shape ranges are best at: filtering, transforming, and only materializing when you finally need owned results.

When to materialize

Materialize when one of these becomes true:

std::vector<std::string> cached(view.begin(), view.end());

That one line makes the ownership change explicit.

Common pitfalls

Reading a pipeline well

Views are powerful, but readability still wins. If a pipeline becomes dense, split it.

Exercises

Example in practice

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> values{4, 1, 3, 2};
    std::sort(values.begin(), values.end());
    return std::binary_search(values.begin(), values.end(), 3) ? 0 : 1;
}

Try this variation

Replace the hand-written search loop with a ranges pipeline. It highlights when composition improves clarity and when it becomes too indirect.

#include <ranges>
#include <vector>

int main() {
    std::vector<int> values{1, 2, 3, 4, 5, 6};
    auto odd = values | std::views::filter([](int value) { return value % 2 == 1; });
    return *odd.begin();
}