libcruft-util/cruft/util/ranges/adjacent.hpp

178 lines
4.7 KiB
C++
Raw Normal View History

#pragma once
#include <array>
#include <ranges>
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 <std::ranges::forward_range V, std::size_t N>
requires std::ranges::view<V> and (N > 0)
class adjacent_view
: public std::ranges::view_interface<adjacent_view<V, N>>
{
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<std::ranges::range_value_t<V>, N>;
using difference_type = std::ranges::range_difference_t<V>;
private:
// Uses an index sequence in the hope to avoid default initialisation and copying for
// problematic value_type.
template <std::size_t ...I>
value_type
dereference (std::index_sequence<I...>) const
{
return { *m_cursors[I]..., };
}
std::array<std::ranges::iterator_t<V>, N> m_cursors;
public:
iterator ();
iterator (
std::ranges::iterator_t<V> first,
std::ranges::sentinel_t<V> 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<N> ());
}
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<V> m_end;
public:
sentinel (std::ranges::sentinel_t<V> 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 <std::size_t N>
struct adjacent_adapter
: std::ranges::range_adaptor_closure<adjacent_adapter<N>>
{
template <std::ranges::forward_range V>
decltype (auto)
operator () (V &&v) const
{
return adjacent_view<V, N> (
std::forward<V> (v)
);
}
};
}
template <std::size_t N>
constexpr detail::adjacent_adapter<N> adjacent;
constexpr detail::adjacent_adapter<2> pairwise;
}
template <class V, std::size_t N>
constexpr bool std::ranges::enable_borrowed_range<cruft::ranges::adjacent_view<V, N>> =
std::ranges::forward_range<V> && std::ranges::enable_borrowed_range<V>;