With the introduction of C++23, the Standard Library has added a number of enhancements to the ranges library that make it easier to write concise, expressive code for manipulating sequences. The key new components you’ll want to know about are:
std::ranges::views– lazy views that can be composed to build pipelinesstd::ranges::to– a terminal operation that materializes a view into a containerstd::ranges::actions– in‑place modifications for views that can be turned into actions
Below is a step‑by‑step guide to using these tools to perform a common task: filter a list of integers, double each value, and store the result in a new `std::vector
`. “`cpp #include #include #include #include int main() { std::vector data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 1. Create a pipeline of views: filter, transform, and then collect. auto result = data | std::ranges::views::filter([](int x){ return x % 2 == 0; }) // keep evens | std::ranges::views::transform([](int x){ return x * 2; }) // double them | std::ranges::to(); // materialize // 2. Print the result for (int v : result) { std::cout << v << ' '; } std::cout << '\n'; } “` ### Breaking Down the Pipeline 1. **`filter` view** The `filter` view takes a predicate and lazily excludes elements that do not satisfy it. It does not perform any copying; the underlying container (`data`) remains untouched. 2. **`transform` view** `transform` applies a unary function to each element that passes through the view chain. Like `filter`, it is lazy and performs no copies until materialized. 3. **`to` adaptor** The `to` adaptor consumes the view and produces a concrete container. The template argument (`std::vector` in this example) determines the type of container created. Because `to` is a *terminal* operation, it triggers the actual iteration and copying. ### Advantages Over Traditional `std::copy_if` + `std::transform` | Feature | Traditional | C++23 Ranges | |———|————-|————–| | **Readability** | Separate loops or `std::copy_if`/`std::transform` calls | Single pipeline line | | **Performance** | Potential extra passes | One pass through the data | | **Flexibility** | Harder to chain multiple operations | View composition is natural | | **Safety** | Manual indexing, risk of errors | Compile‑time checks and concepts | ### Advanced Usage: In‑Place Actions If you prefer to modify the original container instead of creating a new one, you can use the `std::ranges::actions` library: “`cpp #include #include #include int main() { std::vector data{1,2,3,4,5}; data | std::ranges::actions::remove_if([](int x){ return x % 2 == 0; }) // remove evens | std::ranges::actions::transform([](int& x){ x *= 3; }); // triple odds for (int v : data) std::cout << v << ' '; // prints "3 9" } “` `actions` are *in‑place* manipulators that modify the underlying container directly, making them suitable for scenarios where you want to preserve the original data layout. ### Common Pitfalls 1. **Lifetime of the underlying container** – Views are only references to the original data; ensure the container outlives the view chain. 2. **Copy elision** – `to` may cause copies; if the data is large and you only need a view, keep it lazy. 3. **Compatibility** – `std::ranges::to` is a C++23 feature; it is not available in C++20 or earlier. ### Summary C++23’s ranges extensions make it straightforward to write high‑level, composable code for common container transformations. By chaining `views::filter`, `views::transform`, and `to`, you can replace verbose loops with concise pipelines that are both efficient and expressive. For in‑place modifications, `ranges::actions` provides a powerful alternative. Embrace these tools to modernize your C++ codebase and enjoy cleaner, safer, and faster algorithms.