Algorithms, Blockchain and Cloud

Tutorial on C++ Ranges


C++20 introduced ranges, a powerful and elegant abstraction for working with sequences (like arrays, vectors, etc.). Ranges improve readability, composability, and performance compared to raw iterators or old-style loops.

What Are Ranges?

A range in C++20 is an abstraction that represents a sequence of elements that can be iterated over. It pairs well with views and actions like filtering, transforming, and more.

Traditional vs Range-based loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <vector>
 
int main() {
    std::vector<int> v = {1, 2, 3, 4};
 
    // Old-style loop
    for (auto it = v.begin(); it != v.end(); ++it)
        std::cout << *it << ' ';
 
    // Range-based for loop (C++11)
    for (auto x : v)
        std::cout << x << ' ';
}
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4};

    // Old-style loop
    for (auto it = v.begin(); it != v.end(); ++it)
        std::cout << *it << ' ';

    // Range-based for loop (C++11)
    for (auto x : v)
        std::cout << x << ' ';
}

Range Views

Views are lazy, composable operations over ranges. They don’t create copies unless needed.

Filter and Transform Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <vector>
#include <ranges>
 
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6};
 
    auto even_doubled = v 
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * 2; });
 
    for (int n : even_doubled)
        std::cout << n << ' ';  // Output: 4 8 12
}
#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6};

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

    for (int n : even_doubled)
        std::cout << n << ' ';  // Output: 4 8 12
}

Common Views

View Description
std::views::filter Keep elements matching a condition
std::views::transform Apply a function to each element
std::views::take(n) First n elements
std::views::drop(n) Skip first n elements
std::views::reverse Reversed range
std::views::iota(a, b) Range from a to b-1

Using iota and reverse

1
2
3
4
5
6
7
#include <ranges>
#include <iostream>
 
int main() {
    for (int i : std::views::iota(1, 6) | std::views::reverse)
        std::cout << i << ' '; // Output: 5 4 3 2 1
}
#include <ranges>
#include <iostream>

int main() {
    for (int i : std::views::iota(1, 6) | std::views::reverse)
        std::cout << i << ' '; // Output: 5 4 3 2 1
}

Composing Views

You can chain views fluently using the pipe operator |.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <vector>
#include <ranges>
#include <iostream>
 
int main() {
    std::vector<int> v = {5, 10, 15, 20};
 
    auto result = v 
        | std::views::transform([](int x) { return x + 1; })
        | std::views::filter([](int x) { return x % 2 == 0; });
 
    for (int x : result)
        std::cout << x << ' '; // Output: 6 16
}
#include <vector>
#include <ranges>
#include <iostream>

int main() {
    std::vector<int> v = {5, 10, 15, 20};

    auto result = v 
        | std::views::transform([](int x) { return x + 1; })
        | std::views::filter([](int x) { return x % 2 == 0; });

    for (int x : result)
        std::cout << x << ' '; // Output: 6 16
}

Task-Based Examples

1. Filter Even Numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>
#include <ranges>
 
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
 
    auto evens = numbers 
        | std::views::filter([](int n) { return n % 2 == 0; });
 
    for (int n : evens)
        std::cout << n << ' ';  // Output: 2 4 6
}
#include <iostream>
#include <vector>
#include <ranges>

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

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

    for (int n : evens)
        std::cout << n << ' ';  // Output: 2 4 6
}

2. Double the Odd Numbers

1
2
3
4
5
6
7
8
9
10
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
 
    auto doubled_odds = numbers
        | std::views::filter([](int n) { return n % 2 != 0; })
        | std::views::transform([](int n) { return n * 2; });
 
    for (int n : doubled_odds)
        std::cout << n << ' ';  // Output: 2 6 10
}
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

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

    for (int n : doubled_odds)
        std::cout << n << ' ';  // Output: 2 6 10
}

3. Reverse a Sequence

1
2
3
4
5
6
7
8
int main() {
    std::vector<int> nums = {10, 20, 30};
 
    auto reversed = nums | std::views::reverse;
 
    for (int n : reversed)
        std::cout << n << ' ';  // Output: 30 20 10
}
int main() {
    std::vector<int> nums = {10, 20, 30};

    auto reversed = nums | std::views::reverse;

    for (int n : reversed)
        std::cout << n << ' ';  // Output: 30 20 10
}

4. Generate Range of Numbers

1
2
3
4
5
6
#include <ranges>
 
int main() {
    for (int i : std::views::iota(1, 6))
        std::cout << i << ' ';  // Output: 1 2 3 4 5
}
#include <ranges>

int main() {
    for (int i : std::views::iota(1, 6))
        std::cout << i << ' ';  // Output: 1 2 3 4 5
}

5. Take First N Elements

1
2
3
4
5
6
7
int main() {
    auto infinite = std::views::iota(1); // Infinite stream
    auto first5 = infinite | std::views::take(5);
 
    for (int i : first5)
        std::cout << i << ' ';  // Output: 1 2 3 4 5
}
int main() {
    auto infinite = std::views::iota(1); // Infinite stream
    auto first5 = infinite | std::views::take(5);

    for (int i : first5)
        std::cout << i << ' ';  // Output: 1 2 3 4 5
}

6. Sum of Squares of First 5 Odd Numbers

1
2
3
4
5
6
7
8
9
10
11
#include <numeric>
 
int main() {
    auto odd_squares = std::views::iota(1)
        | std::views::filter([](int x) { return x % 2 == 1; })
        | std::views::transform([](int x) { return x * x; })
        | std::views::take(5);
 
    int sum = std::accumulate(odd_squares.begin(), odd_squares.end(), 0);
    std::cout << "Sum = " << sum << '\n'; // Output: Sum = 165
}
#include <numeric>

int main() {
    auto odd_squares = std::views::iota(1)
        | std::views::filter([](int x) { return x % 2 == 1; })
        | std::views::transform([](int x) { return x * x; })
        | std::views::take(5);

    int sum = std::accumulate(odd_squares.begin(), odd_squares.end(), 0);
    std::cout << "Sum = " << sum << '\n'; // Output: Sum = 165
}

7. Check if All Are Positive

1
2
3
4
5
6
7
8
9
10
11
12
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>
 
int main() {
    std::vector<int> nums = {1, 2, 3};
 
    bool all_positive = std::ranges::all_of(nums, [](int n) { return n > 0; });
 
    std::cout << std::boolalpha << all_positive << '\n'; // Output: true
}
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3};

    bool all_positive = std::ranges::all_of(nums, [](int n) { return n > 0; });

    std::cout << std::boolalpha << all_positive << '\n'; // Output: true
}

8. Make a Range Pipeline Function

1
2
3
4
5
6
7
8
9
10
11
12
auto pipeline = [](const std::vector<int>& v) {
    return v 
        | std::views::filter([](int x) { return x % 2 == 0; })
        | std::views::transform([](int x) { return x * 10; });
};
 
int main() {
    std::vector<int> nums = {1, 2, 3, 4};
 
    for (int x : pipeline(nums))
        std::cout << x << ' '; // Output: 20 40
}
auto pipeline = [](const std::vector<int>& v) {
    return v 
        | std::views::filter([](int x) { return x % 2 == 0; })
        | std::views::transform([](int x) { return x * 10; });
};

int main() {
    std::vector<int> nums = {1, 2, 3, 4};

    for (int x : pipeline(nums))
        std::cout << x << ' '; // Output: 20 40
}

Performance Notes

  • Ranges are lazy: elements are processed only when needed.
  • No unnecessary allocations or copies.
  • Excellent for large datasets or pipelines.

When Not to Use Ranges

  • In performance-critical inner loops where STL abstractions are too slow.
  • If your team isn’t using C++20 yet.

References

C/C++ Programming

–EOF (The Ultimate Computing & Technology Blog) —

996 words
Last Post: Tutorial on C++ Smart Pointers
Next Post: const vs constexpr in C++

The Permanent URL is: Tutorial on C++ Ranges (AMP Version)

Exit mobile version