C++ Ranges

Modern functional programming with containers and algorithms

🔄 What are C++ Ranges?

C++ Ranges provide a modern way to work with sequences of data using functional programming style. They make algorithms more readable, composable, and efficient by chaining operations together seamlessly.


// Modern ranges approach
#include <ranges>
#include <vector>
#include <iostream>

std::vector<int> numbers = {1, 2, 3, 4, 5, 6};

auto result = numbers 
    | std::views::filter([](int n) { return n % 2 == 0; })
    | std::views::transform([](int n) { return n * n; });

// result contains: 4, 16, 36
                                    

Output:

Filtered and squared even numbers: 4, 16, 36

Key Ranges Features

🔗

Composable

Chain operations with pipe operator

data | filter | transform | take
âš¡

Lazy Evaluation

Process data only when needed

// No work done until iteration
auto view = data | filter | take(5);
📖

Readable

Express intent clearly

// Clear: "filter then transform"
data | views::filter(pred) 
     | views::transform(func)
🎯

Efficient

No unnecessary copies or allocations

// Views don't copy data
auto view = container | views::reverse;

🔹 Basic Range Operations

Basic range operations in C++20 Ranges allow filtering, querying, and inspecting sequences without modifying them directly. For example, checking how many numbers in a range exceed a value (like 30) is a common filtering task. Similarly, verifying whether all elements satisfy a condition—such as being positive—is a useful query. These operations form the foundation of the Ranges library, providing a more expressive and composable alternative to traditional loops and algorithms. Ranges work seamlessly with standard containers and can dramatically improve code clarity and maintainability in data processing tasks.

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // Filter even numbers
    auto evens = numbers | std::views::filter([](int n) { 
        return n % 2 == 0; 
    });
    
    // Transform to squares
    auto squares = numbers | std::views::transform([](int n) { 
        return n * n; 
    });
    
    // Take first 3 elements
    auto first_three = numbers | std::views::take(3);
    
    // Print results
    for (int n : evens) std::cout << n << " ";
    std::cout << std::endl;
    
    return 0;
}

Output:

Even numbers: 2 4 6 8 10

Squares: 1 4 9 16 25 36 49 64 81 100

First three: 1 2 3

🔹 Chaining Operations

Chaining range operations creates readable pipelines that process data sequentially without intermediate storage. For instance, data | filter(pred) | transform(func) | take(3) filters, transforms, and limits results in one expression. This functional approach enhances clarity and maintainability by separating concerns into distinct steps. It also leverages compiler optimizations for efficient execution. Chaining is central to modern C++ ranges, enabling complex data transformations with minimal code.

#include <ranges>
#include <vector>
#include <iostream>
#include <string>

int main() {
    std::vector<std::string> words = {
        "hello", "world", "cpp", "ranges", "are", "awesome"
    };
    
    // Chain: filter long words, transform to uppercase, take 2
    auto result = words 
        | std::views::filter([](const std::string& s) { 
            return s.length() > 3; 
        })
        | std::views::transform([](const std::string& s) { 
            std::string upper = s;
            std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
            return upper;
        })
        | std::views::take(2);
    
    for (const auto& word : result) {
        std::cout << word << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Output:

HELLO WORLD

🔹 Range Algorithms

Range-based algorithms like std::ranges::find and std::ranges::count_if operate directly on ranges without begin/end iterators. They support projections and predicates, making common operations more concise. For example, std::ranges::find(numbers, 5) locates a value. These algorithms are composable with views and work with any range type, improving code expressiveness and reducing iterator boilerplate. They are a key part of the C++20 ranges library for modern, clean algorithmic code.

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

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // Sort using ranges
    std::ranges::sort(numbers);
    
    // Find element
    auto it = std::ranges::find(numbers, 5);
    if (it != numbers.end()) {
        std::cout << "Found 5 at position: " 
                  << std::distance(numbers.begin(), it) << std::endl;
    }
    
    // Count elements greater than 3
    auto count = std::ranges::count_if(numbers, [](int n) { 
        return n > 3; 
    });
    
    std::cout << "Numbers > 3: " << count << std::endl;
    
    return 0;
}

Output:

Found 5 at position: 4

Numbers > 3: 4

🧠 Test Your Knowledge

What operator is used to chain range operations?