CppRanges (C++20)

Ranges (C++20)

The <ranges> library, introduced in C++20, rethinks how you compose operations over sequences. Instead of calling separate algorithms one after another and manually managing intermediate containers or iterator pairs, ranges let you describe a pipeline of views — lazily-evaluated, composable transformations — much like functional-style pipelines in other languages. Nothing in a ranges pipeline actually runs element-by-element until you iterate the final result, and no intermediate storage is allocated for the steps in between.
The pipe syntax
Views are chained with the pipe operator |, reading naturally left to right: take a range, filter it, transform it, and so on. Two of the most commonly used views are std::views::filter (keep only elements matching a predicate) and std::views::transform (lazily map each element through a function).

Old multi-step algorithms vs a ranges pipeline

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

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Before ranges: separate steps, an intermediate container required
    std::vector<int> evens;
    std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens),
                 [](int n) { return n % 2 == 0; });
    std::vector<int> squaredEvens;
    std::transform(evens.begin(), evens.end(), std::back_inserter(squaredEvens),
                   [](int n) { return n * n; });

    // With ranges: one readable, lazily-evaluated pipeline
    auto pipeline = nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; });

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

    return 0;
}
Because pipeline is a view, none of the filtering or transforming happens when it is declared — it happens lazily, one element at a time, as the final for loop pulls elements through it. This avoids allocating the intermediate evens vector entirely, and reads as a single declarative statement of intent rather than a sequence of imperative steps.
Why this matters
Ranges are widely considered one of the most significant usability improvements to the STL since its original design. They remove the need to constantly pass .begin()/.end() pairs around (a range can be passed as a single object), they compose naturally without intermediate containers, and their lazy evaluation means you only pay for the elements you actually consume — useful for short-circuiting operations over very large or even infinite sequences.
Compiler support varies
C++20 ranges support has matured steadily across GCC, Clang, and MSVC, but the depth of what's implemented — especially newer additions layered on in C++23 like std::views::zip or range-based versions of more algorithms — differs by compiler and version. Always check your specific target toolchain's documentation for its current level of ranges support before relying on more advanced view adaptors in production code.
  • std::ranges (C++20) provides composable, lazily-evaluated views over sequences.

  • Views are chained with the pipe operator |, e.g. std::views::filter | std::views::transform.

  • Pipelines avoid intermediate containers and only compute elements as they are consumed.

  • Ranges are a major ergonomic upgrade to the STL, though compiler support for newer view adaptors still varies.