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:
base() membersize() memberThe iterator type has:
base type membervalue_type type memberHere is a demo: https://godbolt.org/z/fM5dz5zzf
I can't figure out what's missing.
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:
- Verify that your iterator is an
input_iterator.- Verify that your sentinel is a
sentinel_foryour iterator.Those are the checks that will tell you what functionality you're missing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With