diff --git a/cruft/util/CMakeLists.txt b/cruft/util/CMakeLists.txt index 815a920c..ce7fd682 100644 --- a/cruft/util/CMakeLists.txt +++ b/cruft/util/CMakeLists.txt @@ -544,6 +544,7 @@ list ( random.hpp range.cpp range.hpp + ranges/adjacent.hpp ranges/chunk.hpp ranges/enumerate.hpp ranges/izip.hpp diff --git a/cruft/util/ranges/adjacent.hpp b/cruft/util/ranges/adjacent.hpp new file mode 100644 index 00000000..5848940a --- /dev/null +++ b/cruft/util/ranges/adjacent.hpp @@ -0,0 +1,177 @@ +#pragma once + +#include +#include + + +namespace cruft::ranges { + /// A very naive implementation of std::views::adjacent provided because clang-19 lacks it + /// + /// clang#19: check me again in clang-20 + /// + /// Models the main iterator as an array of iterators, and the sentinel as a thin wrapper + // around the underlying range's sentinel. + template + requires std::ranges::view and (N > 0) + class adjacent_view + : public std::ranges::view_interface> + { + private: + V base_; + + public: + class sentinel; + + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using iterator_concept = std::forward_iterator_tag; + using value_type = std::array, N>; + using difference_type = std::ranges::range_difference_t; + + private: + // Uses an index sequence in the hope to avoid default initialisation and copying for + // problematic value_type. + template + value_type + dereference (std::index_sequence) const + { + return { *m_cursors[I]..., }; + } + + std::array, N> m_cursors; + + public: + iterator (); + + iterator ( + std::ranges::iterator_t first, + std::ranges::sentinel_t last_ + ) { + for (std::size_t i = 0; i < N; ++i) { + m_cursors[i] = first; + if (first != last_) + ++first; + } + } + + iterator (iterator const&) = default; + iterator (iterator &&) noexcept = default; + + iterator& operator= (iterator const&) = default; + iterator& operator= (iterator &&) noexcept = default; + + iterator& + operator++ () + { + for (auto &i: m_cursors) + ++i; + return *this; + } + + iterator operator++ (int) const; + + value_type + operator* () const + { + return dereference (std::make_index_sequence ()); + } + + bool + operator==(iterator const &rhs) const + { + return m_cursors.back () == rhs.m_cursors.back (); + } + + bool + operator== (sentinel const &rhs) const + { + return m_cursors.back () == rhs.inner (); + } + }; + + class sentinel { + private: + std::ranges::sentinel_t m_end; + + public: + sentinel (std::ranges::sentinel_t end_) + : m_end (std::move (end_)) + { ; } + + sentinel () = default; + + sentinel (sentinel const&) = default; + sentinel (sentinel&&) = default; + + sentinel& operator= (sentinel const&) = default; + sentinel& operator= (sentinel &&) noexcept = default; + + auto const& + inner (void) const& + { return m_end; } + }; + + adjacent_view (V base) + : base_ (std::move (base)) + { ; } + + iterator + begin (void) const + { + return iterator ( + std::ranges::begin (base_), + std::ranges::end (base_) + ); + } + + iterator + begin (void) + { + return iterator ( + std::ranges::begin (base_), + std::ranges::end (base_) + ); + } + + sentinel + end (void) + { + return sentinel { std::ranges::end (base_) }; + } + + sentinel + end (void) const + { + return sentinel { std::ranges::end (base_) }; + } + }; + + + namespace detail { + // Subclasses `closure` so we get the pipe operator and such. + template + struct adjacent_adapter + : std::ranges::range_adaptor_closure> + { + template + decltype (auto) + operator () (V &&v) const + { + return adjacent_view ( + std::forward (v) + ); + } + }; + } + + template + constexpr detail::adjacent_adapter adjacent; + + constexpr detail::adjacent_adapter<2> pairwise; +} + + +template +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 b50c1d41..87029a9a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -94,6 +94,7 @@ if (TESTS) rand/generator/normal random range + ranges/adjacent ranges/chunk rational region diff --git a/test/ranges/adjacent.cpp b/test/ranges/adjacent.cpp new file mode 100644 index 00000000..97cae2d6 --- /dev/null +++ b/test/ranges/adjacent.cpp @@ -0,0 +1,29 @@ +#include + +#include + +#include + +int main() +{ + static int constexpr INPUT[] = { 1, 1, 2, 3, 5, 8 }; + static int constexpr EXPECTED[][2] = { + { 1, 1 }, + { 1, 2 }, + { 2, 3 }, + { 3, 5 }, + { 5, 8 }, + }; + + cruft::TAP::logger tap; + + const bool same = std::ranges::equal ( + std::views::all (INPUT) | cruft::ranges::pairwise, + EXPECTED, + [] (auto const &a, auto const &b) { return std::ranges::equal (a, b); } + ); + + tap.expect (same, "ranges::pairwise fibonacci"); + + return tap.status (); +} \ No newline at end of file