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) —
GD Star Rating
loading...
1013 words
Last Post:
Tutorial on C++ Smart Pointers Next Post:
const vs constexpr in C++