**Leveraging Ranges and Views in C++20 for Cleaner Code**

C++20 introduces the Ranges library, which dramatically changes how we process sequences. Instead of writing loops, temporary containers, and manual copies, ranges let us express intent directly on the data. Views are lazy transformations that avoid allocating intermediate storage, while ranges bring algorithms that operate over arbitrary ranges.


1. Why Ranges?

Before C++20, most algorithms required iterators or full containers:

std::vector <int> v = {1,2,3,4,5};
std::vector <int> result;
std::copy_if(v.begin(), v.end(), std::back_inserter(result),
             [](int x){ return x % 2 == 0; });

This code copies matching elements into a new vector. With ranges, you can express the same idea as a pipeline:

auto evens = v | std::views::filter([](int x){ return x % 2 == 0; });
for (int n : evens) std::cout << n << ' ';

No temporary vector is created; the filter view lazily evaluates each element on demand.


2. The Core Components

Component Purpose
std::ranges::views Namespace containing lazy, composable transformations
std::ranges::action In-place transformations (e.g., std::ranges::sort)
std::ranges::begin/end Generic accessors that work with any range
std::ranges::size Size query that works on containers and views

3. Building a Pipeline

Below is a complete example that:

  1. Reads a file line by line.
  2. Filters lines containing the word “error”.
  3. Transforms those lines to uppercase.
  4. Counts how many error lines were found.
#include <iostream>
#include <fstream>
#include <string>
#include <ranges>
#include <algorithm>
#include <cctype>

int main() {
    std::ifstream file("log.txt");
    if (!file) return 1;

    auto lines = std::views::istream(file);

    auto error_lines = lines
        | std::views::filter([](const std::string& s){
            return s.find("error") != std::string::npos;
          })
        | std::views::transform([](std::string s){
            std::transform(s.begin(), s.end(), s.begin(),
                           [](unsigned char c){ return std::toupper(c); });
            return s;
          });

    for (auto& line : error_lines) {
        std::cout << line << '\n';
    }

    std::cout << "Total errors: " << std::ranges::distance(error_lines) << '\n';
}

Key takeaways:

  • istream View: std::views::istream lazily reads lines as needed.
  • Filter View: Eliminates non-error lines without storing them.
  • Transform View: Performs the uppercase conversion on the fly.
  • Distance: Works on the view to count elements without iterating twice.

4. Common View Types

View Description
std::views::filter Selects elements that satisfy a predicate
std::views::transform Applies a unary function to each element
std::views::take / take_while Limits the number of elements
std::views::drop / drop_while Skips elements
std::views::reverse Provides a reversed view
std::views::zip (from third‑party) Pairs elements from multiple ranges

5. Performance Notes

  • Lazy evaluation: Views compute elements only when iterated.
  • No temporary containers: Reduces memory overhead and improves cache locality.
  • Small function objects: Prefer constexpr lambdas to enable compile‑time optimization.

6. Advanced Usage

a. Custom View

You can write a view that generates Fibonacci numbers:

struct fibonacci_view : std::ranges::view_interface <fibonacci_view> {
    struct iterator {
        std::size_t cur = 0, next = 1;
        std::size_t operator*() const { return cur; }
        iterator& operator++() {
            std::size_t tmp = cur + next;
            cur = next; next = tmp;
            return *this;
        }
        bool operator!=(const iterator&) const { return true; } // infinite
    };

    iterator begin() const { return {}; }
    std::ranges::sentinel_t <iterator> end() const { return {}; }
};

b. Action vs View

If you need to modify a container in place:

std::vector <int> vec = {3,1,4,1,5};
vec | std::ranges::sort;
vec | std::ranges::reverse;

The std::ranges::action pipeline mutates vec directly.


7. Bottom Line

Ranges and views transform C++ code from a “loop‑heavy” language to a “pipeline‑oriented” one. By composing small, lazy transformations, you write more declarative, readable, and efficient code. The C++20 standard encourages this style; modern IDEs and compilers optimize these pipelines aggressively, making them a powerful tool for every C++ developer.

发表评论