From 0a7cd59eb9a87cb1009ea9a2a4828d461f912538 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Fri, 19 Jul 2024 16:41:10 +1000 Subject: [PATCH] ranges: add a (simplistic) implementation of chunk Taken from the illustrative text at https://en.cppreference.com/w/cpp/ranges/chunk_view --- conanfile.txt | 1 - cruft/util/CMakeLists.txt | 1 + cruft/util/ranges/chunk.hpp | 265 ++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/ranges/chunk.cpp | 42 ++++++ 5 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 cruft/util/ranges/chunk.hpp create mode 100644 test/ranges/chunk.cpp diff --git a/conanfile.txt b/conanfile.txt index 7fe23b48..8877d6e5 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,3 +1,2 @@ [requires] fmt/10.1.1 -range-v3/0.11.0 diff --git a/cruft/util/CMakeLists.txt b/cruft/util/CMakeLists.txt index 449f59ff..94c8d0a7 100644 --- a/cruft/util/CMakeLists.txt +++ b/cruft/util/CMakeLists.txt @@ -544,6 +544,7 @@ list ( random.hpp range.cpp range.hpp + ranges/chunk.hpp rational.cpp rational.hpp region.cpp diff --git a/cruft/util/ranges/chunk.hpp b/cruft/util/ranges/chunk.hpp new file mode 100644 index 00000000..0dbc90f9 --- /dev/null +++ b/cruft/util/ranges/chunk.hpp @@ -0,0 +1,265 @@ +#pragma once + +#include +#include + +#include +#include + + +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 + requires std::ranges::forward_range + class chunk_view : public std::ranges::view_interface> { + private: + V base_; + std::ranges::range_difference_t n_; + + public: + template + class iterator { + friend chunk_view; + + private: + using Parent = std::conditional_t; + using Base = std::conditional_t; + + std::ranges::iterator_t current_; + std::ranges::iterator_t end_; + std::ranges::range_difference_t n_; + std::ranges::range_difference_t 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; + + iterator() = default; + + constexpr + iterator (iterator i) + requires + Const and + std::convertible_to, std::ranges::iterator_t> and + std::convertible_to, std::ranges::sentinel_t> + : 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 current, + std::ranges::range_difference_t 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 + { + std::ranges::advance(current_, missing_ - n_); + missing_ = 0; + return *this; + } + + constexpr iterator + operator+= (difference_type x) + requires std::ranges::random_access_range + { + 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 + { + 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 && + std::three_way_comparable> + { + return x.current_ <=> y.current_; + } + }; + + public: + constexpr explicit + chunk_view (V base, std::ranges::range_difference_t n) + : base_ (std::move (base)) + , n_ (n) + { ; } + + constexpr V + base () const& + requires std::copy_constructible + { return base_; } + + constexpr V base() && + { return std::move(base_); } + + constexpr auto + begin () + { + return iterator(this, std::ranges::begin(base_)); + } + + constexpr auto + begin () const + requires std::ranges::forward_range + { + return iterator( + this, + std::ranges::begin(base_) + ); + } + + constexpr auto + end () + { + if constexpr (std::ranges::common_range and std::ranges::sized_range) + { + auto missing = (n_ - std::ranges::distance (base_) % n_) % n_; + return iterator(this, std::ranges::end (base_), missing); + } + else if constexpr (std::ranges::common_range and !std::ranges::bidirectional_range) + return iterator(this, std::ranges::end(base_)); + else + return std::default_sentinel; + } + + constexpr auto + end () const requires std::ranges::forward_range + { + if constexpr (std::ranges::common_range and std::ranges::sized_range) { + auto missing = (n_ - std::ranges::distance (base_) % n_) % n_; + return iterator(this, std::ranges::end (base_), missing); + } + else if constexpr (std::ranges::common_range and !std::ranges::bidirectional_range) + return iterator(this, std::ranges::end(base_)); + else + return std::default_sentinel; + } + }; + + template + chunk_view (R&&, std::ranges::range_difference_t) -> chunk_view>; + + // 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 { + int n; + + template + constexpr auto + operator() (R &&t) const + { + return chunk_view (std::forward (t), n); + } + }; + + + struct chunk_base { + template + constexpr auto + operator() ( R&& r, std::ranges::range_difference_t n ) const + { + return chunk_view (std::views::all (std::forward (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> = + std::ranges::forward_range && std::ranges::enable_borrowed_range; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a6681b7f..b50c1d41 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -94,6 +94,7 @@ if (TESTS) rand/generator/normal random range + ranges/chunk rational region registrar diff --git a/test/ranges/chunk.cpp b/test/ranges/chunk.cpp new file mode 100644 index 00000000..88d5978c --- /dev/null +++ b/test/ranges/chunk.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + + +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 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 (); +} \ No newline at end of file