ranges: add a (simplistic) implementation of chunk
Taken from the illustrative text at https://en.cppreference.com/w/cpp/ranges/chunk_view
This commit is contained in:
parent
738ba64101
commit
0a7cd59eb9
@ -1,3 +1,2 @@
|
||||
[requires]
|
||||
fmt/10.1.1
|
||||
range-v3/0.11.0
|
||||
|
@ -544,6 +544,7 @@ list (
|
||||
random.hpp
|
||||
range.cpp
|
||||
range.hpp
|
||||
ranges/chunk.hpp
|
||||
rational.cpp
|
||||
rational.hpp
|
||||
region.cpp
|
||||
|
265
cruft/util/ranges/chunk.hpp
Normal file
265
cruft/util/ranges/chunk.hpp
Normal file
@ -0,0 +1,265 @@
|
||||
#pragma once
|
||||
|
||||
#include <ranges>
|
||||
#include <type_traits>
|
||||
|
||||
#include <cruft/util/platform.hpp>
|
||||
#include <cruft/util/debug/assert.hpp>
|
||||
|
||||
|
||||
namespace cruft::ranges {
|
||||
// Largely based on the exposition elements of the specification at
|
||||
// https://en.cppreference.com/w/cpp/ranges/chunk_view
|
||||
//
|
||||
// A lot of the implementation is simplified by assuming we're only working with forward ranges.
|
||||
// But we should remove this code when clang-19 gets released.
|
||||
//
|
||||
// clang#18: remove me when clang gets std::views::chunk
|
||||
template <std::ranges::view V>
|
||||
requires std::ranges::forward_range<V>
|
||||
class chunk_view : public std::ranges::view_interface<chunk_view<V>> {
|
||||
private:
|
||||
V base_;
|
||||
std::ranges::range_difference_t<V> n_;
|
||||
|
||||
public:
|
||||
template <bool Const>
|
||||
class iterator {
|
||||
friend chunk_view;
|
||||
|
||||
private:
|
||||
using Parent = std::conditional_t<Const, const chunk_view, chunk_view>;
|
||||
using Base = std::conditional_t<Const, const V, V>;
|
||||
|
||||
std::ranges::iterator_t<Base> current_;
|
||||
std::ranges::iterator_t<Base> end_;
|
||||
std::ranges::range_difference_t<Base> n_;
|
||||
std::ranges::range_difference_t<Base> missing_;
|
||||
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
// HACK: assuming we're modelling only forward iterator
|
||||
using iterator_concept = std::forward_iterator_tag;
|
||||
|
||||
using value_type = decltype(
|
||||
std::views::take(
|
||||
std::ranges::subrange(current_, end_),
|
||||
n_
|
||||
)
|
||||
);
|
||||
using difference_type = std::ranges::range_difference_t<Base>;
|
||||
|
||||
iterator() = default;
|
||||
|
||||
constexpr
|
||||
iterator (iterator<!Const> i)
|
||||
requires
|
||||
Const and
|
||||
std::convertible_to<std::ranges::iterator_t<V>, std::ranges::iterator_t<Base>> and
|
||||
std::convertible_to<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<Base>>
|
||||
: current_ (std::move (i.current_))
|
||||
, end_ (std::move (i.end_))
|
||||
, n_ (i.n_)
|
||||
, missing_ (i.missing_)
|
||||
{ ; }
|
||||
private:
|
||||
constexpr
|
||||
iterator (
|
||||
Parent* parent,
|
||||
std::ranges::iterator_t<Base> current,
|
||||
std::ranges::range_difference_t<Base> missing = 0
|
||||
) : current_ (current)
|
||||
, end_ (std::ranges::end (parent->base_))
|
||||
, n_ (parent->n_)
|
||||
, missing_ (missing)
|
||||
{}
|
||||
|
||||
public:
|
||||
constexpr value_type
|
||||
operator*() const
|
||||
{
|
||||
CHECK_NEQ (current_, end_);
|
||||
return std::views::take (
|
||||
std::ranges::subrange (current_, end_), n_
|
||||
);
|
||||
}
|
||||
|
||||
constexpr iterator&
|
||||
operator++()
|
||||
{
|
||||
missing_ = std::ranges::advance(current_, n_, end_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr iterator
|
||||
operator++(int)
|
||||
{
|
||||
auto tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
constexpr iterator&
|
||||
operator--()
|
||||
requires std::ranges::bidirectional_range<Base>
|
||||
{
|
||||
std::ranges::advance(current_, missing_ - n_);
|
||||
missing_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr iterator
|
||||
operator+= (difference_type x)
|
||||
requires std::ranges::random_access_range<Base>
|
||||
{
|
||||
if (x > 0) {
|
||||
CHECK (std::ranges::distance(current_, end_) > n_ * (x - 1));
|
||||
std::ranges::advance(current_, n_ * (x - 1));
|
||||
missing_ = std::ranges::advance(current_, n_, end_);
|
||||
}
|
||||
else if (x < 0) {
|
||||
std::ranges::advance(current_, n_ * x + missing_);
|
||||
missing_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr iterator&
|
||||
operator-= (difference_type x)
|
||||
requires std::ranges::random_access_range<Base>
|
||||
{
|
||||
return *this += -x;
|
||||
}
|
||||
|
||||
friend constexpr
|
||||
bool
|
||||
operator== (iterator const& x, iterator const &y)
|
||||
{
|
||||
return x.current_ == y.current_;
|
||||
}
|
||||
|
||||
friend constexpr
|
||||
bool
|
||||
operator== (iterator const& x, std::default_sentinel_t)
|
||||
{
|
||||
return x.current_ == x.end_;
|
||||
}
|
||||
|
||||
friend constexpr
|
||||
auto
|
||||
operator<=> (iterator const& x, iterator const& y)
|
||||
requires
|
||||
std::ranges::random_access_range<Base> &&
|
||||
std::three_way_comparable<std::ranges::iterator_t<Base>>
|
||||
{
|
||||
return x.current_ <=> y.current_;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
constexpr explicit
|
||||
chunk_view (V base, std::ranges::range_difference_t<V> n)
|
||||
: base_ (std::move (base))
|
||||
, n_ (n)
|
||||
{ ; }
|
||||
|
||||
constexpr V
|
||||
base () const&
|
||||
requires std::copy_constructible<V>
|
||||
{ return base_; }
|
||||
|
||||
constexpr V base() &&
|
||||
{ return std::move(base_); }
|
||||
|
||||
constexpr auto
|
||||
begin ()
|
||||
{
|
||||
return iterator<false>(this, std::ranges::begin(base_));
|
||||
}
|
||||
|
||||
constexpr auto
|
||||
begin () const
|
||||
requires std::ranges::forward_range<const V>
|
||||
{
|
||||
return iterator<true>(
|
||||
this,
|
||||
std::ranges::begin(base_)
|
||||
);
|
||||
}
|
||||
|
||||
constexpr auto
|
||||
end ()
|
||||
{
|
||||
if constexpr (std::ranges::common_range<V> and std::ranges::sized_range<V>)
|
||||
{
|
||||
auto missing = (n_ - std::ranges::distance (base_) % n_) % n_;
|
||||
return iterator<false>(this, std::ranges::end (base_), missing);
|
||||
}
|
||||
else if constexpr (std::ranges::common_range<V> and !std::ranges::bidirectional_range<V>)
|
||||
return iterator<false>(this, std::ranges::end(base_));
|
||||
else
|
||||
return std::default_sentinel;
|
||||
}
|
||||
|
||||
constexpr auto
|
||||
end () const requires std::ranges::forward_range<const V>
|
||||
{
|
||||
if constexpr (std::ranges::common_range<const V> and std::ranges::sized_range<const V>) {
|
||||
auto missing = (n_ - std::ranges::distance (base_) % n_) % n_;
|
||||
return iterator<true>(this, std::ranges::end (base_), missing);
|
||||
}
|
||||
else if constexpr (std::ranges::common_range<const V> and !std::ranges::bidirectional_range<const V>)
|
||||
return iterator<true>(this, std::ranges::end(base_));
|
||||
else
|
||||
return std::default_sentinel;
|
||||
}
|
||||
};
|
||||
|
||||
template <class R>
|
||||
chunk_view (R&&, std::ranges::range_difference_t<R>) -> chunk_view<std::views::all_t<R>>;
|
||||
|
||||
// HACK: clang#18 does not expose range_adaptor_closure, so we dig into the internals.
|
||||
#if defined(COMPILER_GCC)
|
||||
#define _CRUFT_RANGE_ADAPTOR_BASE std::ranges::range_adaptor_closure
|
||||
#elif defined(COMPILER_CLANG)
|
||||
#define _CRUFT_RANGE_ADAPTOR_BASE std::__range_adaptor_closure
|
||||
#endif
|
||||
|
||||
|
||||
namespace detail {
|
||||
struct chunk_closure_t : _CRUFT_RANGE_ADAPTOR_BASE<chunk_closure_t> {
|
||||
int n;
|
||||
|
||||
template <typename R>
|
||||
constexpr auto
|
||||
operator() (R &&t) const
|
||||
{
|
||||
return chunk_view (std::forward<R> (t), n);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct chunk_base {
|
||||
template <std::ranges::viewable_range R>
|
||||
constexpr auto
|
||||
operator() ( R&& r, std::ranges::range_difference_t<R> n ) const
|
||||
{
|
||||
return chunk_view (std::views::all (std::forward<R> (r)), n);
|
||||
}
|
||||
|
||||
|
||||
constexpr auto
|
||||
operator() (int n) const
|
||||
{
|
||||
return chunk_closure_t {.n = n};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constexpr detail::chunk_base chunk;
|
||||
}
|
||||
|
||||
template< class V >
|
||||
constexpr bool std::ranges::enable_borrowed_range<cruft::ranges::chunk_view<V>> =
|
||||
std::ranges::forward_range<V> && std::ranges::enable_borrowed_range<V>;
|
@ -94,6 +94,7 @@ if (TESTS)
|
||||
rand/generator/normal
|
||||
random
|
||||
range
|
||||
ranges/chunk
|
||||
rational
|
||||
region
|
||||
registrar
|
||||
|
42
test/ranges/chunk.cpp
Normal file
42
test/ranges/chunk.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include <cruft/util/tap.hpp>
|
||||
|
||||
#include <cruft/util/ranges/chunk.hpp>
|
||||
#include <cruft/util/view.hpp>
|
||||
|
||||
|
||||
int main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
static constexpr int src[] = {
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8,
|
||||
};
|
||||
|
||||
static constexpr int expected[] = {
|
||||
1, 2, 3, -1,
|
||||
4, 5, 6, -2,
|
||||
7, 8, -3,
|
||||
};
|
||||
|
||||
std::vector<int> dst;
|
||||
dst.reserve (std::size (expected));
|
||||
|
||||
// Copy out the src array in groups of 3, inserting a decrementing 'line number' value.
|
||||
// It's a little easier that manually juggling iterators and such.
|
||||
|
||||
int line = 0;
|
||||
for (auto const chunk: src | cruft::ranges::chunk (3)) {
|
||||
// This could be made more efficient by directly insert the chunk in one go,
|
||||
// but it's better to exercise the single iteration case for increased code coverage.
|
||||
for (auto const &i: chunk)
|
||||
dst.push_back (i);
|
||||
|
||||
dst.push_back (--line);
|
||||
}
|
||||
|
||||
tap.expect (cruft::equal (cruft::view (dst), cruft::view (expected)), "chunk(3)");
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user