Loading...
Searching...
No Matches
iterator.hpp
1#pragma once
2
3#include <array>
4#include <cassert>
5#include <concepts>
6#include <cstddef>
7#include <tuple>
8#include <type_traits>
9
10namespace tf {
11
28template <std::integral T>
29constexpr bool is_index_range_invalid(T beg, T end, T step) {
30 return ((step == T{0} && beg != end) ||
31 (beg < end && step <= T{0}) || // positive range
32 (beg > end && step >= T{0})); // negative range
33}
34
70template <std::integral T>
71constexpr size_t distance(T beg, T end, T step) {
72 if constexpr (std::is_unsigned_v<T>) {
73 return end > beg
74 ? static_cast<size_t>((end - beg + step - T{1}) / step)
75 : size_t{0};
76 } else {
77 return static_cast<size_t>(
78 std::max(T{0}, (end - beg + step + (step > T{0} ? T{-1} : T{1})) / step)
79 );
80 }
81}
82
83// ============================================================================
84// IndexRanges<T, N>
85//
86// A single class template representing an N-dimensional index range, the
87// Cartesian product of N independent 1D ranges (begin, end, step). Each
88// dimension is stored as a std::tuple<T, T, T>, so dim(d) returns a tuple
89// that can be read or mutated directly, including via structured bindings:
90//
91// auto& [beg, end, step] = ranges.dim(0);
92//
93// Members that only make sense for a single dimension (begin(), end(),
94// step_size(), reset(), unravel()) are gated with `requires (N == 1)`.
95// Members that only make sense for more than one dimension (ceil(), floor(),
96// upper_slice(), lower_slice()) are
97// gated with `requires (N > 1)`. This keeps
98// everything in one class body — instead of a primary template plus a
99// partial specialization — which Doxygen can parse and cross-reference
100// cleanly.
101//
102// tf::IndexRange<T> is an alias for tf::IndexRanges<T, 1>, the common 1D
103// case.
104//
105// Iteration order for N > 1 is row-major (the last dimension is innermost /
106// fastest), matching the natural loop nesting:
107//
108// for i in dim[0]: // outermost
109// for j in dim[1]:
110// ...
111// for k in dim[N-1]: // innermost
112//
113// Flat index 0 corresponds to (beg[0], beg[1], ..., beg[N-1]).
114// ============================================================================
115
187template <std::integral T, size_t N = 1>
189
190public:
191
195 using index_type = T;
196
200 static constexpr size_t rank = N;
201
202 // --------------------------------------------------------------------------
203 // Construction
204 // --------------------------------------------------------------------------
205
213 IndexRanges() = default;
214
226 explicit IndexRanges(T beg, T end, T step_size) requires (N == 1)
227 : _dims{ std::tuple<T, T, T>{beg, end, step_size} } {}
228
251 template <typename... Ranges>
252 requires (sizeof...(Ranges) == N) &&
253 (std::same_as<std::decay_t<Ranges>, IndexRanges<T, 1>> && ...)
254 explicit IndexRanges(Ranges&&... ranges)
255 : _dims{ ranges.dim(0)... } {}
256
273 explicit IndexRanges(const std::array<std::tuple<T, T, T>, N>& dims) : _dims{dims} {}
274
275 // --------------------------------------------------------------------------
276 // Dimension access (available for any N)
277 // --------------------------------------------------------------------------
278
297 const std::tuple<T, T, T>& dim(size_t d) const { return _dims[d]; }
298
330 std::tuple<T, T, T>& dim(size_t d) { return _dims[d]; }
331
332 // --------------------------------------------------------------------------
333 // 1D convenience accessors (only available when N == 1)
334 // --------------------------------------------------------------------------
335
346 T begin() const requires (N == 1) { return std::get<0>(_dims[0]); }
347
358 T end() const requires (N == 1) { return std::get<1>(_dims[0]); }
359
370 T step_size() const requires (N == 1) { return std::get<2>(_dims[0]); }
371
396 IndexRanges& reset(T beg, T end, T step_size) requires (N == 1);
397
410 IndexRanges& begin(T new_begin) requires (N == 1);
411
424 IndexRanges& end(T new_end) requires (N == 1);
425
438 IndexRanges& step_size(T new_step_size) requires (N == 1);
439
465 IndexRanges unravel(size_t part_beg, size_t part_end) const requires (N == 1);
466
467 // --------------------------------------------------------------------------
468 // Size queries (available for any N)
469 // --------------------------------------------------------------------------
470
491 size_t size(size_t d) const { return std::apply(distance<T>, _dims[d]); }
492
521 size_t size() const;
522
523private:
524
525 std::array<std::tuple<T, T, T>, N> _dims;
526};
527
528// ============================================================================
529// Out-of-class definitions — 1D convenience accessors and size queries
530// ============================================================================
531
532template <std::integral T, size_t N>
534IndexRanges<T, N>::reset(T beg, T end, T step_size) requires (N == 1) {
535 _dims[0] = {beg, end, step_size};
536 return *this;
537}
538
539template <std::integral T, size_t N>
541IndexRanges<T, N>::begin(T new_begin) requires (N == 1) {
542 std::get<0>(_dims[0]) = new_begin;
543 return *this;
544}
545
546template <std::integral T, size_t N>
548IndexRanges<T, N>::end(T new_end) requires (N == 1) {
549 std::get<1>(_dims[0]) = new_end;
550 return *this;
551}
552
553template <std::integral T, size_t N>
555IndexRanges<T, N>::step_size(T new_step_size) requires (N == 1) {
556 std::get<2>(_dims[0]) = new_step_size;
557 return *this;
558}
559
560template <std::integral T, size_t N>
562IndexRanges<T, N>::unravel(size_t part_beg, size_t part_end) const requires (N == 1) {
563 auto [beg, end, step] = _dims[0];
564 return IndexRanges(
565 static_cast<T>(part_beg) * step + beg,
566 static_cast<T>(part_end) * step + beg,
567 step
568 );
569}
570
571template <std::integral T, size_t N>
573 if constexpr (N == 1) {
574 return size(0);
575 } else {
576 // Compile-time-unrolled recursion: D is a template parameter, so each
577 // depth is a distinct instantiation and the recursion fully unrolls
578 // (no runtime loop). `self` is passed explicitly because C++20 lambdas
579 // cannot otherwise name themselves for recursion.
580 auto compute = [this]<size_t D>(auto& self, size_t total) -> size_t {
581 if constexpr (D == N) {
582 return total;
583 } else {
584 size_t s = this->size(D);
585 if (s == 0) return D == 0 ? 0 : total; // outermost zero -> 0, inner zero -> outer product
586 return self.template operator()<D + 1>(self, total * s);
587 }
588 };
589 return compute.template operator()<0>(compute, 1);
590 }
591}
592
593// ----------------------------------------------------------------------------
594// IndexRange<T> — alias for the common 1D case
595// ----------------------------------------------------------------------------
596
611template <std::integral T>
613
614// ==========================================
615// traits
616// ==========================================
617
622template <typename>
623constexpr bool is_index_ranges_v = false;
624
633template <typename T, size_t N>
635
644template <typename R>
646
647// ------------------------------------------------------------------------------------------------
648// Input Iterator Concept
649// ------------------------------------------------------------------------------------------------
650
676template<typename T>
677concept InputIteratorLike = std::input_iterator<std::decay_t<std::unwrap_ref_decay_t<T>>>;
678
679
680} // end of namespace tf -----------------------------------------------------
681
682
class to create an N-dimensional index range of integral indices
Definition iterator.hpp:188
IndexRanges & reset(T beg, T end, T step_size)
updates the range with a new starting index, ending index, and step size (only available when N == 1)
Definition iterator.hpp:534
T end() const
Definition iterator.hpp:358
const std::tuple< T, T, T > & dim(size_t d) const
Definition iterator.hpp:297
IndexRanges & end(T new_end)
updates the ending index of the range (only available when N == 1)
Definition iterator.hpp:548
std::tuple< T, T, T > & dim(size_t d)
returns the (begin, end, step) tuple for dimension d (mutable)
Definition iterator.hpp:330
IndexRanges unravel(size_t part_beg, size_t part_end) const
maps a contiguous index partition back to the corresponding subrange (only available when N == 1)
Definition iterator.hpp:562
IndexRanges & step_size(T new_step_size)
updates the step size of the range (only available when N == 1)
Definition iterator.hpp:555
static constexpr size_t rank
Definition iterator.hpp:200
IndexRanges()=default
constructs an index range without initialization
size_t size(size_t d) const
returns the number of iterations along dimension d
Definition iterator.hpp:491
IndexRanges(Ranges &&... ranges)
constructs an N-D index range from N 1D ranges
Definition iterator.hpp:254
size_t size() const
returns the number of active flat iterations
Definition iterator.hpp:572
T index_type
alias for the index type
Definition iterator.hpp:195
IndexRanges(const std::array< std::tuple< T, T, T >, N > &dims)
constructs an index range from an array of (begin, end, step) tuples
Definition iterator.hpp:273
IndexRanges(T beg, T end, T step_size)
constructs a 1D index range (only available when N == 1)
Definition iterator.hpp:226
IndexRanges & begin(T new_begin)
updates the starting index of the range (only available when N == 1)
Definition iterator.hpp:541
T begin() const
queries the starting index of the range (only available when N == 1)
Definition iterator.hpp:346
T step_size() const
Definition iterator.hpp:370
concept to check if a type is a tf::IndexRanges, regardless of dimensionality
Definition iterator.hpp:645
concept to check if a type is a stateful input iterator
Definition iterator.hpp:677
taskflow namespace
Definition small_vector.hpp:20
IndexRanges< T, 1 > IndexRange
alias for the common 1D case of tf::IndexRanges
Definition iterator.hpp:612
constexpr bool is_index_ranges_v
base type trait to detect if a type is a tf::IndexRanges
Definition iterator.hpp:623
constexpr size_t distance(T beg, T end, T step)
calculates the number of iterations in the given index range
Definition iterator.hpp:71
constexpr bool is_index_range_invalid(T beg, T end, T step)
checks if the given index range is invalid
Definition iterator.hpp:29