1

I'm building a custom range adaptor, that I called replicate_view, that should return each element of a view N times. Here is an example:

for (auto i : std::views::iota(1, 5) | 
              views::replicate(2))
   std::cout << i << '\n';

This should print: 1 1 2 2 3 3 4 4

However, if I add something after it, it doesn't work:

for (auto i : std::views::iota(1, 5) | 
              views::replicate(2) |
              std::views::take(2))
   std::cout << i << '\n';

This gives errors.

VC++:

error C2678: binary '|': no operator found which takes a left-hand operand of type 'n805::replicate_view<std::ranges::iota_view<_Ty1,_Ty2>>' (or there is no acceptable conversion)

Clang:

error: invalid operands to binary expression ('replicate_view<std::ranges::views::all_t<iota_view<int, int>>>' (aka 'replicate_view<std::ranges::iota_view<int, int>>') and 'std::__range_adaptor_closure_t<std::__bind_back_t<std::ranges::views::__take::__fn, std::tuple<int>>>')

Here is my implementation.

Iterator & sentinel

   template <typename R>
   struct replicate_iterator;

   template <typename R>
   struct replicate_sentinel
   {
      using base = std::ranges::iterator_t<R>;
      using size_type = std::ranges::range_difference_t<R>;

      constexpr replicate_sentinel(base end) : end_{ end } {}
      constexpr bool is_at_end(replicate_iterator<R> it);

   private:
      base      end_;
   };

   template <typename R>
   struct replicate_iterator : std::ranges::iterator_t<R>
   {
      using base = std::ranges::iterator_t<R>;
      using value_type = typename std::ranges::range_value_t<R>;

      constexpr replicate_iterator(base start, std::ranges::range_difference_t<R> count) :
         pos_{ start }, count_{ count }
      {
      }

      constexpr replicate_iterator operator++(int)
      {
         if (step_ == count_)
         {
            step_ = 1;
            pos_++;
         }
         else
         {
            step_++;
         }

         return pos_;
      }

      constexpr replicate_iterator& operator++()
      {
         if (step_ == count_)
         {
            step_ = 1;
            pos_++;
         }
         else
         {
            step_++;
         }

         return (*this);
      }

      constexpr value_type operator*() const
      {
         return *pos_;
      }

      constexpr bool operator==(replicate_sentinel<R> s)
      {
         return s.is_at_end(*this);
      }

      constexpr base const value() const { return pos_; }

   private:
      base                                pos_;
      std::ranges::range_difference_t<R>  count_;
      std::ranges::range_difference_t<R>  step_ = 1;
   };

   template <typename R>
   constexpr bool replicate_sentinel<R>::is_at_end(replicate_iterator<R> it)
   {
      return end_ == it.value();
   }

The range adaptor

   template<std::ranges::view R>
   struct replicate_view : public std::ranges::view_interface<replicate_view<R>>
   {
   private:
      R                                   base_;
      std::ranges::range_difference_t<R>  count_;

   public:
      replicate_view() = default;

      constexpr replicate_view(R base, std::ranges::range_difference_t<R> count)
         : base_(std::move(base))
         , count_(count)
      {
      }

      constexpr R base() const&
         requires std::copy_constructible<R>
      { return base_; }
      constexpr R base()&& { return std::move(base_); }

      constexpr std::ranges::range_difference_t<R> const& increment() const { return count_; }

      constexpr auto begin() const
      {
         return replicate_iterator<R>(std::ranges::begin(base_), count_);
      }

      constexpr auto end() const
      {
         return replicate_sentinel<R>{std::ranges::end(base_)};
      }

      constexpr auto size() const requires std::ranges::sized_range<R>
      { return count_ * std::ranges::size(base_); }

      constexpr auto size() requires std::ranges::sized_range<R>
      { return count_ * std::ranges::size(base_); }
   };

Deduction guide

   template<class R>
   replicate_view(R&& base, std::ranges::range_difference_t<R> count)
      ->replicate_view<std::ranges::views::all_t<R>>;

Range adaptor object

   namespace details
   {
      struct replicate_view_fn_closure
      {
         std::size_t step_;
         constexpr replicate_view_fn_closure(std::size_t step)
            : step_(step)
         {
         }

         template <std::ranges::sized_range R>
         constexpr auto operator()(R&& r) const
         {
            return replicate_view(std::forward<R>(r), step_);
         }
      };

      struct replicate_view_fn
      {
         template<std::ranges::sized_range R>
         constexpr auto operator () (R&& r, std::size_t step) const
         {
            return replicate_view(std::forward<R>(r), step);
         }

         constexpr auto operator () (std::size_t step) const
         {
            return replicate_view_fn_closure(step);
         }
      };

      template <std::ranges::sized_range R>
      constexpr auto operator | (R&& r, replicate_view_fn_closure&& a)
      {
         return std::forward<replicate_view_fn_closure>(a)(std::forward<R>(r));
      }
   }

   namespace views
   {
      inline constexpr details::replicate_view_fn replicate;
   }

The range adaptor type has:

  • default constructor
  • base() member
  • size() member

The iterator type has:

  • base type member
  • value_type type member

Here is a demo: https://godbolt.org/z/fM5dz5zzf

I can't figure out what's missing.

Marius Bancila
  • 16,053
  • 9
  • 49
  • 91
  • 1
    `operator*()` shouldn't return `value_type`, it should return the underlying range's reference type. Your const-qualified members (`begin`, `end`, `size`) need to check that `R const` is a range, and then they do the wrong thing - so `begin() const` needs to give you a `replicate_iterator`, etc. – Barry May 12 '22 at 14:15
  • Thank you for your feedback. I made changes based on your comments. – Marius Bancila May 12 '22 at 19:21

1 Answers1

3

First, sentinel_for requires that sentinel must be default_initializable, so you need to add a default constructor for replicate_sentinel.

template <typename R>
struct replicate_sentinel
{
  replicate_sentinel() = default;
  // ...
};

Second, sentinel_for also requires const iterator& to be comparable with const sentinel&, so replicate_iterator::operator== should be const-qualified (I suspect this is just typo)

template <typename R>
struct replicate_iterator : std::ranges::iterator_t<R>
{
  constexpr bool operator==(replicate_sentinel<R> s) const
  {
      return s.is_at_end(*this);
  }
  // ...
};

Demo


Great guidelines from Barry:

The way to check this kind of thing for ranges is:

  1. Verify that your iterator is an input_iterator.
  2. Verify that your sentinel is a sentinel_for your iterator.

Those are the checks that will tell you what functionality you're missing.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90