Advanced Ranges and Views

Advanced Ranges and Views

Lazy pipelines, projections, and view-oriented design beyond basic filter/transform examples.

Advanced Ranges and Views

Build lazy pipelines

auto top_scores = scores
    | std::views::filter([](int score) { return score >= 90; })
    | std::views::transform([](int score) { return score / 10; })
    | std::views::take(5);

Views are lazy and usually cheap to compose.

That means the work happens when you iterate the result, not when you define the pipeline.

auto pipeline = values
    | std::views::filter([](int value) { return value % 2 == 0; })
    | std::views::transform([](int value) { return value * value; });

for (int value : pipeline) {
    std::cout << value << '\n';
}

Projections with algorithms

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

Use projections to avoid small one-off lambdas when sorting or searching by a member.

auto it = std::ranges::find(users, 42, &User::id);

This reads as "find the user whose id is 42" instead of forcing you to write the member access manually.

Views are borrowed, not owned

Safe materialization at an API boundary

auto visible_names = users
    | std::views::filter(&User::active)
    | std::views::transform(&User::name);

std::vector<std::string> stored_names(visible_names.begin(), visible_names.end());

Do this when the result must survive after the source range changes or disappears.

Useful adaptors

Common pipeline shapes

Filter, then transform

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

Drop a prefix, then take a window

auto page = values | std::views::drop(offset) | std::views::take(page_size);

Iterate keys or values from a map-like container

for (const auto& key : config | std::views::keys) {
    std::cout << key << '\n';
}

Practical guidance

When not to use a view pipeline

Quick pitfall reminder

auto bad_view() {
    return std::vector<int>{1, 2, 3} | std::views::filter([](int x) { return x > 1; });
}

This returns a view over a temporary container, which is exactly the sort of lifetime bug ranges can hide if you are not disciplined.

Example in practice

#include <algorithm>
#include <vector>

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

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();
}