When starting to experiment more with C++20 / C++23's std::ranges and std::views, the question inevitably comes up as to what it takes to make a custom view. While there are articles and examples out there, they range from overly simplistic (just an alias/using) to overly complicated. Further, there are many examples, even in popular talks, that simply don't compile.
There are three main ways in which a custom view can interact with ranges/views which we will explain in more detail below.
Full (compiling ;) ) source code for this example can be found here, if you want to read along: https://github.com/int2str/nmpairs
Bubbles!
To keep the example as simple as possible, we will demonstrate a custom nm_pairs_view. This is a specialized version of k-combinations, where k is 2.
In otherwords, the "bubble sort" iterator.
Here's the classic bubble sort algorithm in C++:
``C++
for (size_t n = 0; n != values.size() - 1; ++n) {
for (size_t m = n + 1; m != values.size(); ++m) {
if (values[m] < values[n])
std::swap(values[n], values[m]);
}
}
CODEBLOCK0 C++
auto less = [](const auto& pair) { return pair.second < pair.first; };
auto swap = [](auto pair) { std::swap(pair.first, pair.second); };
std::ranges::for_each(
range | views::nm_pairs | std::views::filter(less), swap);
CODEBLOCK1 C++
class hello_view : public std::ranges::view_interface<hello_view> {
static constexpr auto hello = std::string_view("Hello, World!\n");
public:
auto begin() { return hello.begin(); };
auto end() { return hello.end(); }
};
auto main() -> int {
for (const auto& chr : hello_view{} | std::views::drop(7))
std::print("{}", chr);
}
CODEBLOCK2 C++
template <std::ranges::forward_range RANGE>
requires std::ranges::view<RANGE>
class nm_pairs_view : public std::ranges::view_interface<nm_pairs_view<RANGE>> {
RANGE base_;
public:
[[nodiscard]] constexpr nm_pairs_view() = default;
[[nodiscard]] constexpr explicit nm_pairs_view(RANGE range)
: base_{std::move(range)} {}
...
};
CODEBLOCK3 C++
template <std::ranges::sized_range RANGE>
nm_pairs_view(RANGE&&) -> nm_pairs_view<std::views::all_t<RANGE>>;
CODEBLOCK4 C++
std::vector<int> numbers{1, 2, 3, 4};
for (const auto& [a, b] : nm_pairs_view(numbers))
std::print("[{}, {}] ", a, b);
// Prints: [1, 2] [1, 3] [1, 4] [2, 3] [2, 4] [3, 4]
CODEBLOCK5
C(R)
R | C
CODEBLOCK6 C++
namespace views {
struct nm_pairs_fn : std::ranges::range_adaptor_closure<nm_pairs_fn> {
template <typename RANGE>
constexpr auto operator()(RANGE&& range) const {
return nm_pairs_view{std::forward<RANGE>(range)};
}
};
constexpr inline auto nm_pairs = nm_pairs_fn{};
} // namespace views
CODEBLOCK7 C++
for (const auto& [a, b] : numbers | views::nm_pairs_fn())
...
CODEBLOCK8 C++
for (const auto& [a, b] : numbers | views::nm_pairs)
...
CODEBLOCK9 C++
auto my_vector = std::views::iota(1)
| std::views::take(5)
| std::ranges::to<std::vector>();
CODEBLOCK10 C++
template <typename RANGE>
constexpr CoordinateSet(std::from_range_t /*unused*/, RANGE&& range) {
for (auto coordinate : range) insert(coordinate);
}
CODEBLOCK11 C++
auto coordinate_set = coordinates
| std::views::filter(in_bounds)
| std::ranges::to<CoordinateSet>();
``