Ranges in C++20
C# has Linq
, and Python has built-in functions like filter
and map
for quickly filtering elements that meet specific conditions from a collection. What about C++? Do you have to loop through a container and slowly find the parts you need? No! C++20 introduces the <ranges>
header, which provides many convenient functions to handle such scenarios.
code
The following example demonstrates how to find even numbers from a set, square them, and then reverse the order of the output.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main()
{
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Filter, transform, and sort the numbers
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; }) // Filter even numbers
| std::views::transform([](int n) { return n * n; }) // Square the filtered even numbers
| std::views::reverse; // Reverse the order
// Output the result
for (int n : result) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Execution result
Available Range Adaptors
C++20’s <ranges>
library introduces several range adaptors used to modify or operate on range data. Common adaptors include:
std::views::filter
: Filters elements that meet a condition.std::views::transform
: Applies a transformation to each element.std::views::reverse
: Reverses the order of the range.std::views::take
: Takes the first n elements of the range.std::views::drop
: Drops the first n elements of the range.std::views::take_while
: Takes continuous elements that meet a condition until it fails.std::views::drop_while
: Drops continuous elements that meet a condition until it fails.std::views::concat
: Concatenates multiple ranges.std::views::zip
: Combines elements from multiple ranges into tuples.std::views::join
: Flattens ranges of ranges into a single range.std::views::elements
: Extracts a member or index from elements of the range.
Another Example with Structs
Here is an example with an NBAPlayer
struct:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
// Define NBAPlayer structure
struct NBAPlayer {
std::string name;
int number;
// Friend function for << operator
friend std::ostream& operator<<(std::ostream& os, const NBAPlayer& player)
{
os << "Name: " << player.name << ", Number: " << player.number;
return os;
}
};
int main()
{
// Create a container of NBAPlayers
std::vector<NBAPlayer> players = { {"Kobe Bryant",24},{"MJ",23},{"LBJ", 23}, {"AI",3},{"AD", 0} };
for (auto p : players)
{
std::cout << p << std::endl;
}
// Use range adaptors to filter and transform players
auto filtered_players = players
| std::views::take(5)
| std::views::transform([](NBAPlayer player) {player.number = 24; return player; });
std::cout << std::endl;
std::cout << "---filtered_players---:" << std::endl;
for (auto p : filtered_players)
{
std::cout << p << std::endl;
}
std::cout << std::endl;
std::cout << "---players---:" << std::endl;
for (auto p : players)
{
std::cout << p << std::endl;
}
return 0;
}
Execution result:
Note: Using range adaptors and transformation operations, such as std::views::transform
, does not change the values in the original container.
These adaptors and operations create new “views” or “ranges” over the original data without modifying it.
Usage Scenarios
- Data Filtering: When you need to filter elements that meet specific conditions from a collection, use std::views::filter.
- Data Transformation: When you need to apply a transformation to each element in a collection (e.g., square, negate, format), use std::views::transform.
- Combining Operations: You can chain multiple range adaptors to create complex data processing pipelines without creating temporary containers or explicit iterations.
This is a very convenient feature—give it a try next time!