Creating a custom range in C++ (incomplete)

In this section, we will be exploring how we can create a custom view, including its corresponding range adaptor. For the sake of simplicity, we will (re)implement take_view, which is also present in the STL.

Our goal is that the following code compiles and gives the correct result, as it would with std::views::take:

const std::vector<int> n{2, 3, 5, 6, 7, 8, 9};
auto v = n | rv::filter(is_even) | views::custom_take(2);
std::ranges::copy(v,
std::ostream_iterator<int>(std::cout, " "));

Implementing the view

The first step is to implement the actual view to a range. In this view, we need to store the range and the number of elements this view should process from the range. While implementing this, we apply the range concepts available in C++20. The listing below shows an implementation, which we will walk through next.

template<std::ranges::view R>// #A Using ranges::view concept
class custom_take_view
: public std::ranges::view_interface<custom_take_view<R>> {
  // #B Necessary data members:
  R base_{};
  std::ranges::range_difference_t<R> count_{};

public:
  // #C Default constructible:
  custom_take_view() = default;

  // #D Constructor for range and count:
  constexpr custom_take_view(
    R base,
    std::ranges::range_difference_t<R> count)
    :base_{std::move(base)} ,count_{count}
  {}

  // #E view_interface members:
  constexpr R base() const & {return base_;}
  constexpr R base()&&   {return std::move(base_);}

  // #F Actual begin and end:
  constexpr auto begin() {return std::ranges::begin(base_);}
  constexpr auto end() {
    return std::ranges::next(std::ranges::begin(base_),count_);
  }
};

template<std::ranges::range R>// #G Deduction guide
custom_take_view(R&& base, std::ranges::range_difference_t<R>)
  ->custom_take_view<std::ranges::views::all_t<R>>;

In the template head of the class template custom_take_view, we directly start applying concepts #A. This class should work only with a view type. This concept checks that R is a range that is movable, default initializable, and is a view. The last concept checks whether R is derived from view_base. Our custom_take_view derives from view_base with the help of view_interface, using Curiously Recurring Template Pattern (CRTP).

After the head, we declare the required data members #B. In #C, we ensure that custom_take_view is default-constructible. The next constructor in #D is used when our view is created from a range and a count. This is the second item in the list in ยง3.

The view_interface requires a couple of members, which are implemented in #E. The actual implementation for begin and end is presented in #F. Here, we use the ranges begin version. In end you can see the actual implementation, the only part here that does something other than setting defaults. Using std::ranges::next, we retrieve the next iterator element after begin with an offset of count. With that, we already have a working implementation of custom_take_view.


Please note that this is a simplified version. The STL has to deal with different range types, depending on whether R is a simple_view or a sized_range. We leave this out here.

What is also needed is the Class Template Argument Deduction (CTAD) deduction guide in #G. Without this deduction guide, we cannot create a custom_take_view just by passing a range and a count.

We now have the base for our custom_take_view. Let's make our view fit nicely into all the other ranges and add the missing range adaptor.

A range adaptor for custom_take_view

The next thing we need for custom_take_view is a range adaptor. The code shown below should, in practice, be wrapped in a dedicated namespace like details.

template<std::integral T> // #A Only integrals
struct custom_take_range_adaptor_closure {
  T count_;// #B Store the count
  constexpr custom_take_range_adaptor_closure(T count)
  : count_{count}
  {}

  // #C Allow it to be called with a range:
  template<std::ranges::viewable_range R>
  constexpr auto operator()(R&&r)const {
    return custom_take_view(std::forward<R>(r),count_);
  }
};