C++ custom view for ranges

2 minute read

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);

Categories:

Updated: