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]
|
[requires]
|
||||||
fmt/10.1.1
|
fmt/10.1.1
|
||||||
range-v3/0.11.0
|
|
||||||
|
@ -544,6 +544,7 @@ list (
|
|||||||
random.hpp
|
random.hpp
|
||||||
range.cpp
|
range.cpp
|
||||||
range.hpp
|
range.hpp
|
||||||
|
ranges/chunk.hpp
|
||||||
rational.cpp
|
rational.cpp
|
||||||
rational.hpp
|
rational.hpp
|
||||||
region.cpp
|
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
|
rand/generator/normal
|
||||||
random
|
random
|
||||||
range
|
range
|
||||||
|
ranges/chunk
|
||||||
rational
|
rational
|
||||||
region
|
region
|
||||||
registrar
|
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