C++ custom view for ranges
The following youtube shows a good explanation how we can define the custom view for ranges
Advanced Ranges - Writing Modular, Clean, and Efficient Code with Custom Views - Steve Sorkin
Custom C++23 Range Adaptor Closure Example
This example demonstrates how to implement a custom C++23 range adaptor (a pipeable custom view) using std::ranges::views::adaptor_closure.
π― Goal
Weβll implement a even_filter_view β a view that filters out odd numbers β and make it pipeable like standard adaptors (std::views::filter, std::views::transform, etc.).
β Full Example
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <ranges>
#include <iostream>
#include <vector>
#include <concepts>
// 1οΈβ£ Define the custom view
template <std::ranges::view V>
requires std::integral<std::ranges::range_value_t<V>>
class even_filter_view : public std::ranges::view_interface<even_filter_view<V>> {
private:
V base_; // underlying view
class iterator {
using base_iterator = std::ranges::iterator_t<V>;
base_iterator current_;
base_iterator end_;
void satisfy_predicate() {
while (current_ != end_ && *current_ % 2 != 0)
++current_;
}
public:
using value_type = std::ranges::range_value_t<V>;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
iterator() = default;
iterator(base_iterator current, base_iterator end)
: current_(current), end_(end) {
satisfy_predicate();
}
value_type operator*() const { return *current_; }
iterator& operator++() {
++current_;
satisfy_predicate();
return *this;
}
bool operator==(const iterator&) const = default;
};
public:
even_filter_view() = default;
explicit even_filter_view(V base) : base_(std::move(base)) {}
auto begin() { return iterator(std::ranges::begin(base_), std::ranges::end(base_)); }
auto end() { return iterator(std::ranges::end(base_), std::ranges::end(base_)); }
V base() const { return base_; }
};
// Deduction guide
template <class R>
even_filter_view(R&&) -> even_filter_view<std::views::all_t<R>>;
// 2οΈβ£ Range adaptor closure definition (C++23 way!)
namespace views {
inline constexpr auto even_filter = std::ranges::views::adaptor_closure([](auto&& r) {
return even_filter_view(std::views::all(std::forward<decltype(r)>(r)));
});
}
// 3οΈβ£ Example usage
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
// β
Using C++23 pipe syntax directly
for (int x : nums | views::even_filter)
std::cout << x << " ";
std::cout << "\n";
// β
Combine with standard views
for (int x : nums | views::even_filter | std::views::transform([](int n) { return n * n; }))
std::cout << x << " ";
std::cout << "\n";
}
π§ Output
1
2
2 4 6 8
4 16 36 64
π Whatβs new in C++23
C++23 introduces range adaptor closures using:
1
std::ranges::views::adaptor_closure
This makes custom views behave like standard adaptors β pipeable, composable, and constexpr-friendly.
Advantages
β
Natural pipe syntax (nums | views::even_filter)
β
Composable with standard adaptors
β
Less boilerplate, more expressive code
π§© TL;DR
| C++20 way | C++23 way |
|---|---|
Define a struct with operator() |
Use std::ranges::views::adaptor_closure |
| Not automatically pipeable | Fully pipeable |
| More boilerplate | Cleaner and composable |
Example Composition
1
2
3
4
auto result = nums
| views::even_filter
| std::views::transform([](int n) { return n * 10; })
| std::views::take(3);