实现一种C++20 ranges view: strideview

ranges 是C++20中的重要新增特性。view是ranges的精华,支持组合和惰性求值。ranges还提供了接口,用户可以定制开发自己的view, 如何自行实现view 呢?。view分两种,一种只能作为源,如ranges::ints,而另一种是adaptor,用来调整range或者view的功能。下文是我们自行开发的一种adaptor view: StrideView,和rangs::stride的功能相同:调整步长,如views::ints|stride(2)可以表示偶数。可以看到实现核心的工作是嵌套类adaptor。

#include <vector>
#include <iostream>
#include <range/v3/all.hpp>
namespace views = ranges::views;

template <class Rng>
struct StrideView : ranges::view_adaptor<StrideView<Rng>, Rng> {
  constexpr StrideView() = default;
  friend ranges::range_access;
  using range_type = std::remove_cv_t<Rng>;
  struct adaptor : public ranges::adaptor_base {
    constexpr adaptor() = default;
    template <typename Rng>
    auto begin(Rng& rng) noexcept
    {
      return ranges::begin(rng.base());
    }

    template <class Rng>
    auto end(Rng& rng) noexcept
    {
      return ranges::end(rng.base());
    }
    template <typename I>
    constexpr auto read(I i) const noexcept
    {
      return *i;
    }
    template <class I, class S>
    constexpr bool equal(I b, S e) const noexcept
    {
      return b == e;
    }

    constexpr adaptor(const StrideView* s) noexcept : strideview_(s) {}
    template <typename I>
    constexpr auto distance_to(I this_iter, I that_iter) const noexcept requires ranges::sized_range<range_type>
    {
      auto d = ranges::distance(this_iter, that_iter);
      int  m = d;
      if (d < 0) {
        m = -d;
      }
      int dividor = m / strideview_->stride_;
      int carry   = (m % (strideview_->stride_) + strideview_->stride_ - 1) / strideview_->stride_;
      if (d < 0) {
        return -(dividor + carry);
      } else {
        return dividor + carry;
      }
    }
    template <class I>
    requires ranges::bidirectional_range<range_type> constexpr void prev(I& it) noexcept
    {
      if (it == strideview_->base().end()) {
        ranges::advance(it, -strideview_->step_);
      } else {
        ranges::advance(it, -this->strideview_->stride_);
      }
    }

    template <typename I>
    constexpr void next(I& it) noexcept
    {
      ranges::advance(it, this->strideview_->stride_, strideview_->base().end());
    }

    template <typename I>
    constexpr void advance(I& it, ranges::iter_difference_t<I> n) const noexcept
        requires ranges::random_access_range<range_type>
    {
      if (n >= 0) {
        ranges::advance(it, n * this->strideview_->stride_, strideview_->base().end());
      } else {
        int m = -n;
        if (it == strideview_->base().end()) {
          ranges::advance(it, -(m - 1) * this->strideview_->stride_ - strideview_->step_);
        } else {
          ranges::advance(it, n * this->strideview_->stride_);
        }
      }
    }

   private:
    const StrideView* strideview_;
  };
  constexpr StrideView(Rng&& rng, int stride) noexcept
      requires(ranges::sized_range<Rng>&& ranges::bidirectional_range<Rng>)
      : ranges::view_adaptor<StrideView, Rng>(std::forward<Rng>(rng)),
        stride_(stride),
        size_(rng.size()),
        step_(rng.size() % stride)
  {
    if (!step_) {
      step_ = stride;
    }
  }
  constexpr StrideView(Rng&& rng, int stride) noexcept
      : ranges::view_adaptor<StrideView, Rng>(std::forward<Rng>(rng)), stride_(stride), step_(stride)
  {
  }
  constexpr auto begin_adaptor() const noexcept { return adaptor{this}; }
  constexpr auto end_adaptor() const noexcept { return adaptor{this}; }

 private:
  int stride_;
  int size_;
  int step_;
};

template <class Rng>
auto stride(Rng&& rng, int n) noexcept
{
  return StrideView<views::all_t<Rng>>(views::all(std::forward<Rng>(rng)), n);
}

auto stride(int s) noexcept
{
  return ranges::make_view_closure([s](auto&& rngs) {
    using Rngs = decltype(rngs);
    return StrideView<views::all_t<Rngs>>(views::all(std::forward<Rngs>(rngs)), s);
  });
};

int  main()
{
  std::vector va = views::ints | views::take(20) | ranges::to<std::vector<int>>;
  {
    auto s = va | stride(2) | views::drop(2);
    for (auto i : s) {
      std::cout << i << " ";
    }
    std::cout << std::endl;
  }

  return 0;
}
Posted 2020-10-19