| Taskflow: A General-purpose Task-parallel Programming System

Loading...
Searching...
No Matches
Public Types | Public Member Functions | Static Public Attributes | List of all members
tf::IndexRanges< T, N > Class Template Reference

class to create an N-dimensional index range of integral indices More...

`#include <taskflow/utility/iterator.hpp>`

## Public Types
using | index_type = T
| alias for the index type

## Public Member Functions
| IndexRanges ()=default
| constructs an index range without initialization
| IndexRanges (T beg, T end, T step_size)
| constructs a 1D index range (only available when `N` == 1)
template<typename... Ranges> requires (sizeof...(Ranges) == N) && (std::same_as<std::decay_t<Ranges>, IndexRanges<T, 1>> && ...)
| IndexRanges (Ranges &&... ranges)
| constructs an N-D index range from N 1D ranges
| IndexRanges (const std::array< std::tuple< T, T, T >, N > &dims)
| constructs an index range from an array of (begin, end, step) tuples
const std::tuple< T, T, T > & | dim (size_t d) const
| returns the (begin, end, step) tuple for dimension `d` (read-only)
std::tuple< T, T, T > & | dim (size_t d)
| returns the (begin, end, step) tuple for dimension `d` (mutable)
T | begin () const
| queries the starting index of the range (only available when `N` == 1)
T | end () const
| queries the ending index of the range (only available when `N` == 1)
T | step_size () const
| queries the step size of the range (only available when `N` == 1)
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)
IndexRanges & | begin (T new_begin)
| updates the starting index of the range (only available when `N` == 1)
IndexRanges & | end (T new_end)
| updates the ending index of the range (only available when `N` == 1)
IndexRanges & | step_size (T new_step_size)
| updates the step size of the range (only available when `N` == 1)
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)
size_t | size (size_t d) const
| returns the number of iterations along dimension `d`
size_t | size () const
| returns the number of active flat iterations
size_t | ceil (size_t chunk_size) const
| returns the smallest hyperplane-aligned chunk size that is >= chunk_size, capped at size() when chunk_size exceeds the total active range size (only available when `N` > 1)
size_t | floor (size_t chunk_size) const
| returns the largest hyperplane-aligned chunk size that is <= chunk_size (only available when `N` > 1)
IndexRanges | upper_slice (size_t flat_beg, size_t chunk_size) const
| returns the smallest perfectly-aligned hyperbox reachable from flat_beg, rounding up to the next hyperplane boundary when chunk_size is not aligned (only available when `N` > 1)
IndexRanges | lower_slice (size_t flat_beg, size_t chunk_size) const
| returns the largest perfectly-aligned hyperbox reachable from flat_beg whose size does not exceed chunk_size, rounding down to the previous hyperplane boundary when chunk_size is not aligned (only available when `N` > 1)
IndexRanges | empty_box () const
| returns a box whose `size()` is 0 (only available when `N` > 1)

## Static Public Attributes
static constexpr size_t | rank = N
| the number of dimensions

## Detailed Description

template<std::integral T, size_t N = 1>
class tf::IndexRanges< T, N >

class to create an N-dimensional index range of integral indices

Template Parameters

  T | the integral type of the indices
  N | the number of dimensions (defaults to 1)

This class represents the Cartesian product of `N` independent 1D index ranges, each defined by a starting index, ending index, and step size. Each dimension is stored as a `std::tuple<T, T, T>` of (begin, end, step), accessible and mutable through `dim(d)`.

For `N` == 1, the class behaves like a plain 1D range: `tf::IndexRange<T>` (an alias for `tf::IndexRanges<T, 1>`) exposes convenience accessors `begin()`, `end()`, `step_size()`, `reset()`, and `unravel()` directly, without going through `dim(0)`.

tf::IndexRange<int> range(0, 10, 2);
for(auto i=range.begin(); i<range.end(); i+=range.step_size()) {
printf("%d ", i);
}
tf::IndexRange
IndexRanges< T, 1 > IndexRange
alias for the common 1D case of tf::IndexRanges
Definition iterator.hpp:971

You can reset the range to a different value using tf::IndexRanges::reset. This is particularly useful when the range value is only known at runtime.

tf::IndexRange<int> range;
range.reset(0, 10, 2);
for(auto i=range.begin(); i<range.end(); i+=range.step_size()) {
printf("%d ", i);
}
tf::IndexRanges::reset
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:668
tf::IndexRanges::end
T end() const
queries the ending index of the range (only available when N == 1)
Definition iterator.hpp:358
tf::IndexRanges::begin
T begin() const
queries the starting index of the range (only available when N == 1)
Definition iterator.hpp:346
tf::IndexRanges::step_size
T step_size() const
queries the step size of the range (only available when N == 1)
Definition iterator.hpp:370

Attention
  It is the user's responsibility to ensure the given range is valid. For instance, a range from 0 to 10 with a step size of -2 is invalid.

For `N` > 1, iteration order is row-major: the last dimension varies fastest, matching the natural nesting of C-style for-loops.

// 3D range: i in [0,4), j in [0,6), k in [0,8), all step 1
tf::IndexRanges<int, 3> r(
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 6, 1),
tf::IndexRange<int>(0, 8, 1)
);
printf("%zu\n", r.size()); // 4*6*8 = 192
tf::IndexRanges
class to create an N-dimensional index range of integral indices
Definition iterator.hpp:188

Note
  If any dimension has zero size (e.g. an empty range such as ``[0,0)), the active iteration space stops at that dimension. `size()` returns the product of all outer dimensions before the first zero, and `upper_slice` / `lower_slice` copy the zero-size dimension and all inner dimensions as full extent into each returned sub-box. This matches the behaviour of sequential nested loops:

// Zero in the middle dimension: outer i-loop still runs, j/k body never executes.
tf::IndexRanges<int, 3> r(
tf::IndexRange<int>(0, 100, 1), // i: 100 iters (active)
tf::IndexRange<int>(0, 0, 1), // j: 0 iters (stops accumulation here)
tf::IndexRange<int>(0, 100, 1) // k: 100 iters (inactive — never reached)
);
r.size(); // 100 (only the i-dimension contributes)

## Constructor & Destructor Documentation

## ◆ IndexRanges() [1/4]

template<std::integral T, size_t N = 1>

tf::IndexRanges< T, N >::IndexRanges | ( |  | ) |
| default

constructs an index range without initialization

The per-dimension ranges are left in an indeterminate state. Use this when the bounds will be set later via `reset()` (N==1) or `dim(d)` (any N).

## ◆ IndexRanges() [2/4]

template<std::integral T, size_t N = 1>

tf::IndexRanges< T, N >::IndexRanges | ( | T | beg,
|  | T | end,
|  | T | step_size )
| inline explicit

constructs a 1D index range (only available when `N` == 1)

Parameters

  beg | starting index of the range
  end | ending index of the range (exclusive)
  step_size | step size between consecutive indices in the range

tf::IndexRange<int> range(0, 10, 2); // elements: 0, 2, 4, 6, 8

## ◆ IndexRanges() [3/4]

template<std::integral T, size_t N = 1>
template<typename... Ranges>
requires (sizeof...(Ranges) == N) && (std::same_as<std::decay_t<Ranges>, IndexRanges<T, 1>> && ...)

tf::IndexRanges< T, N >::IndexRanges | ( | Ranges &&... | ranges | ) |
| inline explicit

constructs an N-D index range from N 1D ranges

Parameters

  ranges | exactly N 1D ranges (each a `tf::IndexRanges<T, 1>`, i.e. a `tf::IndexRange<T>`), one per dimension in order from outermost (dim 0) to innermost (dim N-1)

Each 1D range defines the `begin`, `end`, and `step_size` for its dimension. Dimensions are independent — any combination of positive, negative, or zero step sizes is supported, as long as each 1D range is individually valid.

// 3D: mixed step sizes
tf::IndexRanges<int, 3> r3(
tf::IndexRange<int>(0, 4, 1), // dim 0: 4 elements
tf::IndexRange<int>(0, 10, 2), // dim 1: 5 elements (0,2,4,6,8)
tf::IndexRange<int>(0, 6, 1) // dim 2: 6 elements
);
r3.size(); // 120

## ◆ IndexRanges() [4/4]

template<std::integral T, size_t N = 1>

tf::IndexRanges< T, N >::IndexRanges | ( | const std::array< std::tuple< T, T, T >, N > & | dims | ) |
| inline explicit

constructs an index range from an array of (begin, end, step) tuples

Parameters

  dims | `std::array` of exactly N (begin, end, step) tuples

Useful when the per-dimension bounds are constructed programmatically.

std::array<std::tuple<int,int,int>, 2> dims = {
std::tuple{0, 4, 1},
std::tuple{0, 5, 1}
};
tf::IndexRanges<int, 2> r(dims);
r.size(); // 20

## Member Function Documentation

## ◆ begin() [1/2]

template<std::integral T, size_t N = 1>

T tf::IndexRanges< T, N >::begin | ( |  | ) | const
| inline

queries the starting index of the range (only available when `N` == 1)

Returns
  the starting index of the range

tf::IndexRange<int> range(0, 10, 2);
auto b = range.begin(); // b == 0

## ◆ begin() [2/2]

template<std::integral T, size_t N>
requires (N == 1)

IndexRanges< T, N > & tf::IndexRanges< T, N >::begin | ( | T | new_begin | ) |

updates the starting index of the range (only available when `N` == 1)

Parameters

  new_begin | the new starting index of the range

Returns
  a reference to `*this`

tf::IndexRange<int> range(0, 10, 2); // elements: 0, 2, 4, 6, 8
range.begin(4); // elements become: 4, 6, 8

## ◆ ceil()

template<std::integral T, size_t N>
requires (N > 1)

size_t tf::IndexRanges< T, N >::ceil | ( | size_t | chunk_size | ) | const

returns the smallest hyperplane-aligned chunk size that is >= chunk_size, capped at size() when chunk_size exceeds the total active range size (only available when `N` > 1)

Parameters

  chunk_size | hint for the desired number of flat elements

Returns
  the smallest natural per-step volume of the active dimensions that is >= `chunk_size`, or `size()` if `chunk_size` > `size()`, or 0 if the outermost dimension is zero-size

Analogous to `std::ceil` but operating on the discrete set of hyperplane boundary sizes of the active dimensions (those before the first zero-size dimension). Only active suffix products are considered — inactive inner dimensions do not contribute boundaries.

// 6D range: 3 x 4 x 8 x 0 x 2 x 3
// Active dims: d=0(3), d=1(4), d=2(8) -> size()=96
// Active boundaries: 1, 8, 32, 96 (inactive dims d=3,4,5 contribute nothing)
tf::IndexRanges<int, 6> r(
tf::IndexRange<int>(0, 3, 1),
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 8, 1),
tf::IndexRange<int>(0, 0, 1),
tf::IndexRange<int>(0, 2, 1),
tf::IndexRange<int>(0, 3, 1)
);
r.ceil(1); // 1 — already a boundary
r.ceil(5); // 8 — rounds up to next active boundary
r.ceil(33); // 96 — rounds up to full active size
r.ceil(200); // 96 — capped at size()

## ◆ dim() [1/2]

template<std::integral T, size_t N = 1>

std::tuple< T, T, T > & tf::IndexRanges< T, N >::dim | ( | size_t | d | ) |
| inline

returns the (begin, end, step) tuple for dimension `d` (mutable)

Parameters

  d | zero-based dimension index in ``[0, N)

Returns
  a mutable reference to the `std::tuple<T, T, T>` for dimension `d`

Use this to update the bounds of an individual dimension at runtime, for example inside an upstream init task when using stateful ranges:

tf::IndexRanges<int, 2> range;
auto init = taskflow.emplace([&]() {
range.dim(0) = {0, rows, 1}; // set row range at runtime
range.dim(1) = {0, cols, 1}; // set col range at runtime
});
auto loop = taskflow.for_each_by_index(std::ref(range), callable);
init.precede(loop);
tf::IndexRanges::dim
const std::tuple< T, T, T > & dim(size_t d) const
returns the (begin, end, step) tuple for dimension d (read-only)
Definition iterator.hpp:297

Native structured bindings can bind directly to the tuple's elements:

// mutate a single field through a structured binding
auto& [beg, end, step] = range.dim(0);
beg = 0; end = rows; step = 1;

## ◆ dim() [2/2]

template<std::integral T, size_t N = 1>

const std::tuple< T, T, T > & tf::IndexRanges< T, N >::dim | ( | size_t | d | ) | const
| inline

returns the (begin, end, step) tuple for dimension `d` (read-only)

Parameters

  d | zero-based dimension index in ``[0, N)

Returns
  a const reference to the `std::tuple<T, T, T>` for dimension `d`, in (begin, end, step) order

tf::IndexRanges<int, 3> r(
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 10, 2),
tf::IndexRange<int>(0, 6, 1)
);
auto [beg, end, step] = r.dim(1); // beg=0, end=10, step=2

## ◆ empty_box()

template<std::integral T, size_t N>
requires (N > 1)

IndexRanges< T, N > tf::IndexRanges< T, N >::empty_box | ( |  | ) | const

returns a box whose `size()` is 0 (only available when `N` > 1)

Returns
  a degenerate box with the same dimensions as `*this`, except dimension 0 is collapsed to a single point (begin == end)

Collapsing dimension 0 alone is sufficient: `size()` stops accumulating at the first zero-size dimension, so the returned box reports 0 regardless of the other dimensions, which are copied through verbatim.

Used by `upper_slice` and `lower_slice` for the `chunk_size` `==` `0` case, since the returned box's `size()` doubles as the "consumed" count.

## ◆ end() [1/2]

template<std::integral T, size_t N = 1>

T tf::IndexRanges< T, N >::end | ( |  | ) | const
| inline

queries the ending index of the range (only available when `N` == 1)

Returns
  the ending index (exclusive) of the range

tf::IndexRange<int> range(0, 10, 2);
auto e = range.end(); // e == 10

## ◆ end() [2/2]

template<std::integral T, size_t N>
requires (N == 1)

IndexRanges< T, N > & tf::IndexRanges< T, N >::end | ( | T | new_end | ) |

updates the ending index of the range (only available when `N` == 1)

Parameters

  new_end | the new ending index (exclusive) of the range

Returns
  a reference to `*this`

tf::IndexRange<int> range(0, 10, 2); // elements: 0, 2, 4, 6, 8
range.end(6); // elements become: 0, 2, 4

## ◆ floor()

template<std::integral T, size_t N>
requires (N > 1)

size_t tf::IndexRanges< T, N >::floor | ( | size_t | chunk_size | ) | const

returns the largest hyperplane-aligned chunk size that is <= chunk_size (only available when `N` > 1)

Parameters

  chunk_size | hint for the desired number of flat elements

Returns
  the largest natural per-step volume of the active dimensions that is <= `chunk_size`, or 0 if the outermost dimension is zero-size

Analogous to `std::floor`; see `ceil` for the boundary semantics.

// 6D range: 3 x 4 x 8 x 0 x 2 x 3
// Active dims: d=0(3), d=1(4), d=2(8) -> size()=96
// Active boundaries: 1, 8, 32, 96 (inactive dims d=3,4,5 contribute nothing)
tf::IndexRanges<int, 6> r(
tf::IndexRange<int>(0, 3, 1),
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 8, 1),
tf::IndexRange<int>(0, 0, 1),
tf::IndexRange<int>(0, 2, 1),
tf::IndexRange<int>(0, 3, 1)
);
r.floor(5); // 1 — rounds down to previous active boundary
r.floor(33); // 32 — rounds down
r.floor(200); // 96 — capped at size()

## ◆ lower_slice()

template<std::integral T, size_t N>
requires (N > 1)

IndexRanges< T, N > tf::IndexRanges< T, N >::lower_slice | ( | size_t | flat_beg,
|  | size_t | chunk_size ) const

returns the largest perfectly-aligned hyperbox reachable from flat_beg whose size does not exceed chunk_size, rounding down to the previous hyperplane boundary when chunk_size is not aligned (only available when `N` > 1)

Parameters

  flat_beg | starting flat index (row-major) into the active ND space
  chunk_size | hint for the desired number of elements

Returns
  the sub-box; its `size()` gives the number of elements consumed

Analogous to `std::floor:` if `chunk_size` already aligns to a hyperplane boundary the returned box is exact (identical to upper_slice); otherwise it rounds down to the largest box that does not exceed `chunk_size`, so `size()` <= `chunk_size`.

Used by static partitioners: each worker's pre-assigned flat quota is never exceeded, so workers do not double-process elements at quota boundaries.

## ◆ reset()

template<std::integral T, size_t N>
requires (N == 1)

IndexRanges< T, N > & tf::IndexRanges< T, N >::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)

Parameters

  beg | new starting index of the range
  end | new ending index of the range (exclusive)
  step_size | new step size between consecutive indices in the range

Returns
  a reference to `*this`

Use this to rebind a stateful range to new bounds at runtime, for example inside an upstream init task:

tf::IndexRange<int> range;
auto init = taskflow.emplace([&]() {
range.reset(0, 10, 2); // elements: 0, 2, 4, 6, 8
});
auto loop = taskflow.for_each_by_index(std::ref(range), callable);
init.precede(loop);

## ◆ size() [1/2]

template<std::integral T, size_t N>

size_t tf::IndexRanges< T, N >::size | ( |  | ) | const

returns the number of active flat iterations

For `N` == 1 this is simply the element count of the range. For `N` > 1 it returns the product of dimension sizes from the outermost dimension inward, stopping before the first zero-size dimension. This matches the behaviour of sequential nested loops: a zero-size dimension prevents its own iterations and those of all deeper dimensions, but outer dimensions are unaffected.

// All non-zero: 4 * 6 * 8 = 192
tf::IndexRanges<int, 3> r1(
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 6, 1),
tf::IndexRange<int>(0, 8, 1)
);
r1.size(); // 192
// Zero in middle (d=1): outer dim only -> 4
tf::IndexRanges<int, 3> r2(
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 0, 1), // stops accumulation here
tf::IndexRange<int>(0, 8, 1)
);
r2.size(); // 4

## ◆ size() [2/2]

template<std::integral T, size_t N = 1>

size_t tf::IndexRanges< T, N >::size | ( | size_t | d | ) | const
| inline

returns the number of iterations along dimension `d`

Parameters

  d | zero-based dimension index in ``[0, N)

Returns
  the element count of dimension `d`

This is a per-dimension count, independent of other dimensions and of the zero-size stopping rule that `size()` applies.

tf::IndexRanges<int, 3> r(
tf::IndexRange<int>(0, 4, 1),
tf::IndexRange<int>(0, 10, 2),
tf::IndexRange<int>(0, 6, 1)
);
r.size(1); // 5, since the range [0,10) with step 2 has 5 elements

## ◆ step_size() [1/2]

template<std::integral T, size_t N = 1>

T tf::IndexRanges< T, N >::step_size | ( |  | ) | const
| inline

queries the step size of the range (only available when `N` == 1)

Returns
  the step size between consecutive indices in the range

tf::IndexRange<int> range(0, 10, 2);
auto s = range.step_size(); // s == 2

## ◆ step_size() [2/2]

template<std::integral T, size_t N>
requires (N == 1)

IndexRanges< T, N > & tf::IndexRanges< T, N >::step_size | ( | T | new_step_size | ) |

updates the step size of the range (only available when `N` == 1)

Parameters

  new_step_size | the new step size between consecutive indices

Returns
  a reference to `*this`

tf::IndexRange<int> range(0, 10, 1); // elements: 0, 1, 2, ..., 9
range.step_size(3); // elements become: 0, 3, 6, 9

## ◆ unravel()

template<std::integral T, size_t N>
requires (N == 1)

IndexRanges< T, N > tf::IndexRanges< T, N >::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)

Parameters

  part_beg | beginning index of the partition (inclusive)
  part_end | ending index of the partition (exclusive)

Returns
  a new range covering the elements at positions [`part_beg`, `part_end`) in the original range

Each element of the range can be addressed by a zero-based position index from `0` to `size()-1`. This function unravels a contiguous slice of those position indices back into the original iteration space, returning the sub-range whose elements correspond exactly to positions [`part_beg`, `part_end`).

tf::IndexRange<int> range(0, 10, 2); // elements: 0, 2, 4, 6, 8
auto sub = range.unravel(1, 4); // elements at positions [1,4): 2, 4, 6
// sub.begin() == 2, sub.end() == 8, sub.step_size() == 2

Attention
  Users must ensure [`part_beg`, `part_end`) is a valid partition of [0, `size()`), i.e., `part_end` <= size().

## ◆ upper_slice()

template<std::integral T, size_t N>
requires (N > 1)

IndexRanges< T, N > tf::IndexRanges< T, N >::upper_slice | ( | size_t | flat_beg,
|  | size_t | chunk_size ) const

returns the smallest perfectly-aligned hyperbox reachable from flat_beg, rounding up to the next hyperplane boundary when chunk_size is not aligned (only available when `N` > 1)

Parameters

  flat_beg | starting flat index (row-major) into the active ND space
  chunk_size | hint for the desired number of elements

Returns
  the sub-box; its `size()` gives the number of elements consumed

Analogous to `std::ceil:` if `chunk_size` already aligns to a hyperplane boundary the returned box is exact; otherwise it rounds up to the next clean orthogonal boundary, so `size()` >= `chunk_size`.

Only the active dimensions — those before the first zero-size dimension — are partitioned. Dimensions from the first zero onward are copied into the returned box as full extent and do not affect the flat index space.

The trailing-zeros rule applies within the active dimensions: a dimension can only expand if all active dimensions inner to it start at coordinate 0. When this fires the function returns the best geometry-constrained box reachable from `flat_beg`.

Used by dynamic partitioners: the atomic cursor advances by the returned box's `size()` and any overshoot is self-correcting.

The documentation for this class was generated from the following file:

- taskflow/utility/iterator.hpp