A custom C++20 range view

 1 year ago
source link: https://mariusbancila.ro/blog/2020/06/06/a-custom-cpp20-range-view/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A custom C++20 range view

Posted on June 6, 2020 by Marius Bancila

Some time ago, I wrote a short post about the C++20 ranges library with examples of how it can simplify our code. Let me take a brief example. Give a sequence of numbers, print the last two even numbers, but in reverse order. This can be written as follows:

#include <iostream>
#include <ranges>
#include <algorithm>
namespace rv = std::ranges::views;
int main()
    auto is_even = [](int const n) {return n % 2 == 0;};
    std::vector<int> n{1,1,2,3,5,8,13,21,34,55,89,144,233,377};
    auto v = n | rv::filter(is_even) | rv::reverse | rv::take(2) ;
    std::ranges::copy(v, std::ostream_iterator<int>(std::cout, " "));

This will print 144 and 34. Here is a link to a working program.

In this snippet, filter, reverse, and take are range adaptors, also called views. These range adaptors are class templates available in the namespace std::ranges. They are called filter_view, reverse_view, take_view, and so on. There is a total of 16 views, including other useful ones such as take_while_view, drop_view, drop_while, and transform_view. You can find the entire list here.

For simplicity of use, expressions such as views::filter(R, P), views:take(R, N), or views::reverse(R) are available, although their type and value is unspecified. They are expression-equivalent to filter_view(R, P), take_view(R, N), reverse_view(R), etc.

These range adaptors are lazy. They do not process the adapted range until you start iterating on them. They are also composable using the pipe operator, as we have seen in the snippet above. Moreover, we can write our own range adaptors and use them together with the standard ones.

To see how this works, let’s write a simple range adaptor. Below, you can find a custom, minimum implementation of the take adaptor. We will call this custom_take_view. This takes a range and an integer, representing the number of elements to retain from the range.

#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <concepts>
#include <algorithm>
#include <assert.h>
namespace rg = std::ranges;
template<rg::input_range R> requires rg::view<R>
class custom_take_view : public rg::view_interface<custom_take_view<R>>
    R                                         base_ {};
    std::iter_difference_t<rg::iterator_t<R>> count_ {};
    rg::iterator_t<R>                         iter_ {std::begin(base_)};
    custom_take_view() = default;
    constexpr custom_take_view(R base, std::iter_difference_t<rg::iterator_t<R>> count)
        : base_(base)
        , count_(count)
        , iter_(std::begin(base_))
    constexpr R base() const &
    {return base_;}
    constexpr R base() &&
    {return std::move(base_);}
    constexpr auto begin() const
    {return iter_;}
    constexpr auto end() const
    { return std::next(iter_, count_); }
    constexpr auto size() const requires rg::sized_range<const R>
        const auto s = rg::size(base_);
        const auto c = static_cast<decltype(s)>(count_);
        return s < c ? 0 : s - c;    
template<class R>
custom_take_view(R&& base, std::iter_difference_t<rg::iterator_t<R>>)
    -> custom_take_view<rg::views::all_t<R>>;
namespace details
    struct custom_take_range_adaptor_closure
        std::size_t count_;
        constexpr custom_take_range_adaptor_closure(std::size_t count): count_(count)
        template <rg::viewable_range R>
        constexpr auto operator()(R && r) const
            return custom_take_view(std::forward<R>(r), count_);
    struct custom_take_range_adaptor
        template<rg::viewable_range R>
        constexpr auto operator () (R && r, std::iter_difference_t<rg::iterator_t<R>> count)
            return custom_take_view( std::forward<R>(r), count ) ;
        constexpr auto operator () (std::size_t count)
            return custom_take_range_adaptor_closure(count);
    template <rg::viewable_range R>
    constexpr auto operator | (R&& r, custom_take_range_adaptor_closure const & a)
        return a(std::forward<R>(r)) ;
namespace views
    details::custom_take_range_adaptor custom_take;

Having this, we can re-write the snippet as follows:

int main()
    auto is_even = [](int const n) {return n % 2 == 0;};
    std::vector<int> n{1,1,2,3,5,8,13,21,34,55,89,144,233,377};
    auto v = n | rv::filter(is_even) | rv::reverse | views::custom_take(2) ;
    std::ranges::copy(v, std::ostream_iterator<int>(std::cout, " "));

The only restriction for this view, as well as for the standard take_view, is that you must specify a count that does not exceed the actual size of the range.

And here are some tests to make sure the view works as expected.

void are_equal1(std::vector<int> const & input, std::vector<int> const & output, std::size_t const n)
    std::size_t index = 0;
    for(auto const & i : input | views::custom_take(n))
        assert(i == output[index]);
    assert(index == output.size());
void are_equal2(std::vector<int> const & input, std::vector<int> const & output, std::size_t const n)
    std::size_t index = 0;
    for(auto const & i : input | views::custom_take(n) | rg::views::reverse)
        assert(i == output[index]);
    assert(index == output.size());
int main()
    are_equal1({}, {}, 0);
    are_equal1({1,2,3,4,5}, {}, 0);
    are_equal1({1,2,3,4,5}, {1}, 1);
    are_equal1({1,2,3,4,5}, {1,2}, 2);
    are_equal1({1,2,3,4,5}, {1,2,3,4,5}, 5);
    are_equal2({}, {}, 0);
    are_equal2({1,2,3,4,5}, {}, 0);
    are_equal2({1,2,3,4,5}, {1}, 1);
    are_equal2({1,2,3,4,5}, {2,1}, 2);
    are_equal2({1,2,3,4,5}, {5,4,3,2,1}, 5);

Here is a link to a working sample.

The C++20 ranges library is currently only available with GCC, since version 10. If you don’t have the compiler available, you can try it online with Compiler Explorer or Wandbox.

Like this:


CategoriesC++, UncategorizedTagsC++, c++20, ranges

About Joyk

Aggregate valuable and interesting links.
Joyk means Joy of geeK