</div><!-- fragment --><h1><aclass="anchor" id="ParallelIterationsIndexBased"></a>
Create an Index-based Parallel-Iteration Task</h1>
<p>Index-based parallel-for performs parallel iterations over a range <code>[first, last)</code> with the given <code>step</code> size. The task created by <aclass="el" href="classtf_1_1FlowBuilder.html#a3b132bd902331a11b04b4ad66cf8bf77" title="constructs an index-based parallel-for task">tf::Taskflow::for_each_index(B first, E last, S step, C callable, P part)</a> represents parallel execution of the following loop:</p>
<divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keyword">auto</span> i = first; i > last; i += step) {</div>
<divclass="line"> callable(i);</div>
<divclass="line">}</div>
</div><!-- fragment --><p>We support only integer-based ranges. The range can go in a positive or negative direction.</p>
<divclass="fragment"><divclass="line">taskflow.for_each_index(0, 100, 2, [](<spanclass="keywordtype">int</span> i) { }); <spanclass="comment">// 50 iterations with a positive step</span></div>
<divclass="line">taskflow.for_each_index(100, 0, -2, [](<spanclass="keywordtype">int</span> i) { }); <spanclass="comment">// 50 iterations with a negative step</span></div>
</div><!-- fragment --><p>Notice that the direction is defined in terms of the half-open range <code>[first, last)</code>, where <code>last</code> is excluded. In the positive case, the 50 items are 0, 2, 4, 6, ..., 96, 98. In the negative case, the 50 items are 100, 98, 96, 94, ..., 4, 2. An example of the Taskflow graph for the positive case under 5 workers is depicted below:</p>
<divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_parallel_for_1.svg" width="867" height="155"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<p>You can pass indices by reference using <ahref="https://en.cppreference.com/w/cpp/utility/functional/ref">std::ref</a> to marshal parameter updates between dependent tasks. This is useful when the range is not known at the time the task graph is constructed but is initialized by an upstream task.</p>
<divclass="line"> std::cout << <spanclass="stringliteral">"parallel iteration on index "</span> << i << <spanclass="charliteral">'\n'</span>;</div>
<divclass="line"> }</div>
<divclass="line">);</div>
<divclass="line"></div>
<divclass="line"><spanclass="comment">// The code below is wrong! first and last are captured by copy at construction time</span></div>
<divclass="line"><spanclass="comment">// auto pf = taskflow.for_each_index(first, last, 1, [&](int i) { });</span></div>
<divclass="line"></div>
<divclass="line">init.precede(pf);</div>
</div><!-- fragment --><p>When <code>init</code> finishes, the parallel-for task <code>pf</code> will see <code>first</code> as 0 and <code>last</code> as 1000 and performs parallel iterations over the 1000 indices.</p>
Create an IndexRange-based Parallel-Iteration Task</h1>
<p>While <aclass="el" href="classtf_1_1FlowBuilder.html#a3b132bd902331a11b04b4ad66cf8bf77" title="constructs an index-based parallel-for task">tf::Taskflow::for_each_index</a> gives each worker a single scalar index, <aclass="el" href="classtf_1_1FlowBuilder.html#a2582a216d54dacca2b7022ea7e89452a" title="constructs a parallel-for task over a one- or multi-dimensional index range">tf::Taskflow::for_each_by_index</a> gives each worker a contiguous <em>sub-range</em> (or <em>sub-box</em> in multiple dimensions) of the iteration space. This distinction is significant: instead of processing one element at a time, the callable operates on an entire block of consecutive indices in a single invocation, enabling block algorithms that exploit data locality, facilitate SIMD vectorisation, and reduce scheduling overhead.</p>
<dlclass="section user"><dt>What is an IndexRange?</dt><dd></dd></dl>
<p>A <aclass="el" href="namespacetf.html#a6c928ec9248757ba8276e316ef26846b" title="alias for the common 1D case of tf::IndexRanges">tf::IndexRange</a> describes a typed, half-open index range <code>[begin, end)</code> with a step size. There are two variants:</p>
<ul>
<li><code><aclass="el" href="namespacetf.html#a6c928ec9248757ba8276e316ef26846b" title="alias for the common 1D case of tf::IndexRanges">tf::IndexRange</a><T></code> (an alias for <code><aclass="el" href="classtf_1_1IndexRanges.html" title="class to create an N-dimensional index range of integral indices">tf::IndexRanges</a><T, 1></code>): A 1D range of integral indices. It is defined by three parameters: <code>begin</code>, <code>end</code>, and <code>step_size</code>. The elements are <code>begin</code>, <code>begin+step</code>, <code>begin+2*step</code>, ... up to (but not including) <code>end</code>.</li>
<divclass="ttc" id="anamespacetf_html_a6c928ec9248757ba8276e316ef26846b"><divclass="ttname"><ahref="namespacetf.html#a6c928ec9248757ba8276e316ef26846b">tf::IndexRange</a></div><divclass="ttdeci">IndexRanges< T, 1 > IndexRange</div><divclass="ttdoc">alias for the common 1D case of tf::IndexRanges</div><divclass="ttdef"><b>Definition</b> iterator.hpp:612</div></div>
</div><!-- fragment --><divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_indexrange_1d.svg" width="612" height="68"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<ul>
<li><code><aclass="el" href="classtf_1_1IndexRanges.html" title="class to create an N-dimensional index range of integral indices">tf::IndexRanges</a><T, N></code>: An N-dimensional Cartesian product of N independent 1D ranges, one per dimension. Dimension 0 is the outermost (slowest varying) and dimension N-1 is the innermost (fastest varying), matching the natural nesting of C-style for-loops (row-major order). Each dimension is stored as a <code>std::tuple<T, T, T></code> of (begin, end, step_size), accessible and mutable through <code>dim(d)</code>.</li>
</ul>
<divclass="fragment"><divclass="line"><spanclass="comment">// 2D range: i in [0,3), j in [0,4) — a 3x4 grid of 12 index pairs</span></div>
<divclass="ttc" id="aclasstf_1_1IndexRanges_html"><divclass="ttname"><ahref="classtf_1_1IndexRanges.html">tf::IndexRanges</a></div><divclass="ttdoc">class to create an N-dimensional index range of integral indices</div><divclass="ttdef"><b>Definition</b> iterator.hpp:188</div></div>
</div><!-- fragment --><divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_indexrange_2d.svg" width="576" height="474"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<p>The key advantage of <aclass="el" href="classtf_1_1IndexRanges.html">tf::IndexRanges<T, N></a> over a raw scalar index is that the callable receives the full sub-range (or sub-box) at once, allowing it to implement cache-friendly block algorithms, apply SIMD over contiguous indices, or exploit any other block-level optimisation.</p>
<dlclass="section user"><dt>Create a Parallel-Iteration Task over a 1D IndexRange</dt><dd></dd></dl>
<p>Passing a 1D <code><aclass="el" href="namespacetf.html#a6c928ec9248757ba8276e316ef26846b" title="alias for the common 1D case of tf::IndexRanges">tf::IndexRange<T></a></code> to <aclass="el" href="classtf_1_1FlowBuilder.html#a2582a216d54dacca2b7022ea7e89452a" title="constructs a parallel-for task over a one- or multi-dimensional index range">tf::Taskflow::for_each_by_index</a> creates a parallel task whose callable receives one contiguous sub-range of the original range per invocation:</p>
<divclass="line"><spanclass="comment">// sub is a contiguous slice of [0, 100)</span></div>
<divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">int</span> i = sub.<aclass="code hl_function" href="classtf_1_1IndexRanges.html#ae37261f0d2f326449c469233561a3da6">begin</a>(); i < sub.<aclass="code hl_function" href="classtf_1_1IndexRanges.html#a253e15e199f974ee26b2e33a5e2b3cf1">end</a>(); i += sub.<aclass="code hl_function" href="classtf_1_1IndexRanges.html#afcb30e2b9567ad685e702201d2265880">step_size</a>()) {</div>
<divclass="line"> process(i);</div>
<divclass="line"> }</div>
<divclass="line">});</div>
<divclass="ttc" id="aclasstf_1_1IndexRanges_html_a253e15e199f974ee26b2e33a5e2b3cf1"><divclass="ttname"><ahref="classtf_1_1IndexRanges.html#a253e15e199f974ee26b2e33a5e2b3cf1">tf::IndexRanges::end</a></div><divclass="ttdeci">T end() const</div><divclass="ttdoc">queries the ending index of the range (only available when N == 1)</div><divclass="ttdef"><b>Definition</b> iterator.hpp:358</div></div>
<divclass="ttc" id="aclasstf_1_1IndexRanges_html_ae37261f0d2f326449c469233561a3da6"><divclass="ttname"><ahref="classtf_1_1IndexRanges.html#ae37261f0d2f326449c469233561a3da6">tf::IndexRanges::begin</a></div><divclass="ttdeci">T begin() const</div><divclass="ttdoc">queries the starting index of the range (only available when N == 1)</div><divclass="ttdef"><b>Definition</b> iterator.hpp:346</div></div>
<divclass="ttc" id="aclasstf_1_1IndexRanges_html_afcb30e2b9567ad685e702201d2265880"><divclass="ttname"><ahref="classtf_1_1IndexRanges.html#afcb30e2b9567ad685e702201d2265880">tf::IndexRanges::step_size</a></div><divclass="ttdeci">T step_size() const</div><divclass="ttdoc">queries the step size of the range (only available when N == 1)</div><divclass="ttdef"><b>Definition</b> iterator.hpp:370</div></div>
</div><!-- fragment --><p>Because the callable sees the full sub-range at once, it can implement cache-friendly block algorithms that would be impossible when receiving a single index at a time. For instance, a worker assigned <code>[32, 64)</code> can prefetch that entire cache line before processing, rather than incurring per-element dispatch overhead.</p>
<dlclass="section user"><dt>Create a Parallel-Iteration Task over a Multi-dimensional IndexRange</dt><dd></dd></dl>
<p>Passing an N-dimensional <code><aclass="el" href="classtf_1_1IndexRanges.html">tf::IndexRanges<T, N></a></code> to <aclass="el" href="classtf_1_1FlowBuilder.html#a2582a216d54dacca2b7022ea7e89452a" title="constructs a parallel-for task over a one- or multi-dimensional index range">tf::Taskflow::for_each_by_index</a> creates a parallel task whose callable receives one orthogonal <em>sub-box</em> of the full ND space per invocation. A sub-box is a valid hyperrectangle — a contiguous, axis-aligned tile that preserves the original step sizes of every dimension. The figure below shows a 2x3x4 range decomposed into two sub-boxes (one per outer-dimension value), each a 3x4 slice:</p>
<divclass="ttc" id="aclasstf_1_1IndexRanges_html_a4e0162b872edd6176e9d6a308b295427"><divclass="ttname"><ahref="classtf_1_1IndexRanges.html#a4e0162b872edd6176e9d6a308b295427">tf::IndexRanges::dim</a></div><divclass="ttdeci">const std::tuple< T, T, T > & dim(size_t d) const</div><divclass="ttdoc">returns the (begin, end, step) tuple for dimension d (read-only)</div><divclass="ttdef"><b>Definition</b> iterator.hpp:297</div></div>
</div><!-- fragment --><p>Each sub-box preserves the original step sizes for every dimension, so the inner loops are identical to what you would write for sequential code. The same pattern extends naturally to three or more dimensions:</p>
<divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">int</span> k = w0; k < w1; k += ws) {</div>
<divclass="line"> process(i, j, k);</div>
<divclass="line"> }</div>
<divclass="line"> }</div>
<divclass="line"> }</div>
<divclass="line">});</div>
</div><!-- fragment --><dlclass="section user"><dt>Zero-size Dimensions</dt><dd></dd></dl>
<p>When a dimension has zero size, it and all dimensions inner to it produce no iterations — but outer dimensions are unaffected. This matches the behaviour of sequential nested loops: a zero-size inner loop simply never executes, while the outer loops still run.</p>
<divclass="fragment"><divclass="line"><spanclass="comment">// The middle dimension has zero iterations (j_begin == j_end).</span></div>
<divclass="line"><spanclass="comment">// The outer i-loop still has work; the j/k body never executes.</span></div>
<divclass="line"> process(i, j); <spanclass="comment">// <-- never called</span></div>
<divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">int</span> k = k0; k < k1; k += ks) {</div>
<divclass="line"> process(i, j, k); <spanclass="comment">// <-- never called</span></div>
<divclass="line"> }</div>
<divclass="line"> }</div>
<divclass="line"> }</div>
<divclass="line">});</div>
</div><!-- fragment --><p>This is equivalent to the sequential nested loop:</p>
<divclass="fragment"><divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">int</span> i = 0; i < 100; ++i) {</div>
<divclass="line"> process(i); <spanclass="comment">// <-- called for every i</span></div>
<divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">int</span> j = 0; j < 0; ++j) { <spanclass="comment">// zero iterations — body skipped</span></div>
<divclass="line"> process(i, j); <spanclass="comment">// <-- never called</span></div>
<divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">int</span> k = 0; k < 100; ++k) {</div>
<divclass="line"> process(i, j, k); <spanclass="comment">// <-- never called</span></div>
<divclass="line"> }</div>
<divclass="line"> }</div>
<divclass="line">}</div>
</div><!-- fragment --><dlclass="section note"><dt>Note</dt><dd>This differs from OpenMP's <code>collapse</code> clause, which treats the entire iteration space as a Cartesian product and yields zero total iterations when any dimension is empty. Taskflow preserves the sequential semantics of nested loops: outer dimensions always execute independently of inner ones.</dd></dl>
<dlclass="section user"><dt>Understand the Scheduling Algorithm</dt><dd></dd></dl>
<p>Taskflow schedules parallel iterations over an index range by mapping the N-dimensional space to a 1D flat index space and distributing chunks of that flat space among workers. Specifically, the full ND range is first linearized in row-major order into a flat index space of size <code>N</code> = <code>range.size()</code>. For a 3x4 2D range, this looks like:</p>
<divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_indexrange_flat.svg" width="474" height="420"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<p>Then, workers claim contiguous chunks of the flat index space, either pre-assigned (static partitioner) or on demand via an atomic cursor (dynamic partitioners). During the assignment, each flat chunk boundary coincides with a <em>hyperplane</em><em>boundary</em>, i.e., a suffix product of the dimension sizes (1, <code>dim[N-1]</code>, <code>dim[N-1]</code> x <code>dim[N-2]</code>, ...). This is the critical constraint: only boundaries that align to a complete inner row (or slice, or hyper-slice) produce a valid rectangular sub-box. The figure below illustrates why alignment matters for a 3x4 grid:</p>
<p>A cut at flat index 6 (mid-row) would split row <code>i=1</code> between two workers, producing an L-shaped non-rectangular region that cannot be expressed as a single <code><aclass="el" href="classtf_1_1IndexRanges.html" title="class to create an N-dimensional index range of integral indices">IndexRanges</a></code> sub-box. A cut at flat index 8 (row boundary) produces two clean rectangular sub-boxes. Taskflow enforces this automatically. The <code>chunk_size</code> hint is always snapped to the nearest hyperplane boundary via <code>IndexRanges::ceil()</code>:</p>
<divclass="fragment"><divclass="line"><spanclass="comment">// For a 3x4 range with 3 workers:</span></div>
<divclass="line"><spanclass="comment">// raw N/W = 12/3 = 4 -> r.ceil(4) = 4 (already a row boundary)</span></div>
<divclass="line"><spanclass="comment">// For a 3x5 range with 2 workers:</span></div>
<divclass="line"><spanclass="comment">// raw N/W = 15/2 = 7 -> r.ceil(7) = 10 (next row boundary = 2 full rows)</span></div>
</div><!-- fragment --><dlclass="section user"><dt>Static vs dynamic partitioners</dt><dd></dd></dl>
<p>With a static partitioner, each worker's chunk is pre-assigned before execution and never changes. The figure below shows a 3x4 range evenly split among 3 workers, each receiving exactly one aligned row:</p>
<p>With a dynamic partitioner, workers pull chunks from a shared atomic cursor. The chunk may overshoot to the next hyperplane boundary when the requested size does not align exactly, but the cursor self-corrects so no element is visited twice.</p>
<dlclass="section note"><dt>Note</dt><dd>You do not need to think about alignment explicitly. Taskflow handles it automatically — the <code>chunk_size</code> you pass to the partitioner is a <em>hint</em>, and the scheduler snaps it to the nearest valid boundary before distributing work.</dd></dl>
<dlclass="section user"><dt>Capture Range by Reference</dt><dd></dd></dl>
<p>When the range bounds are not known at task-graph construction time, pass the range by <ahref="https://en.cppreference.com/w/cpp/utility/functional/ref">std::ref</a> so that an upstream task can set the bounds before the parallel loop runs. This works for both 1D and multi-dimensional ranges.</p>
<divclass="line"><spanclass="comment">// The code below is wrong! range is captured by copy at construction time</span></div>
<divclass="line"><spanclass="comment">// auto pf = taskflow.for_each_by_index(range, callable);</span></div>
<divclass="line"></div>
<divclass="line">init.precede(pf);</div>
</div><!-- fragment --><p>When <code>init</code> finishes, <code>pf</code> reads the updated range and partitions the <code>rows x cols</code> space among workers.</p>
Create an Iterator-based Parallel-Iteration Task</h1>
<p>Iterator-based parallel-for performs parallel iterations over a range specified by two <ahref="https://en.cppreference.com/w/cpp/iterator/iterator">STL-styled iterators</a>, <code>first</code> and <code>last</code>. The task created by <aclass="el" href="classtf_1_1FlowBuilder.html#a597d2cceaf2a2598a3c4b9f742b0aacc" title="constructs an STL-styled parallel-for task">tf::Taskflow::for_each(B first, E last, C callable, P part)</a> represents parallel execution of the following loop:</p>
<divclass="fragment"><divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keyword">auto</span> i = first; i != last; i++) {</div>
<divclass="line"> callable(*i);</div>
<divclass="line">}</div>
</div><!-- fragment --><p><aclass="el" href="classtf_1_1FlowBuilder.html#a597d2cceaf2a2598a3c4b9f742b0aacc" title="constructs an STL-styled parallel-for task">tf::Taskflow::for_each</a> simultaneously applies the callable to the object obtained by dereferencing every iterator in the range <code>[first, last)</code>. It is the user's responsibility to ensure the range is valid within the execution of the parallel-for task. Iterators must have the post-increment operator <code>++</code> defined.</p>
<divclass="line"> std::cout << <spanclass="stringliteral">"parallel for on item "</span> << i << <spanclass="stringliteral">'\n'</span>;</div>
<divclass="line">});</div>
</div><!-- fragment --><h2><aclass="anchor" id="ParallelForEachCaptureIteratorsByReference"></a>
Capture Iterators by Reference</h2>
<p>Similar to index-based parallel-for, iterators can be passed by reference using <ahref="https://en.cppreference.com/w/cpp/utility/functional/ref">std::ref</a> so that one task can set up the range before another task performs the parallel-for.</p>
<divclass="line"> std::cout << <spanclass="stringliteral">"parallel iteration on item "</span> << i << <spanclass="charliteral">'\n'</span>;</div>
<divclass="line"> }</div>
<divclass="line">);</div>
<divclass="line"></div>
<divclass="line"><spanclass="comment">// The code below is wrong! first and last are captured by copy at construction time</span></div>
<divclass="line"><spanclass="comment">// auto pf = taskflow.for_each(first, last, [](int i) { });</span></div>
<divclass="line"></div>
<divclass="line">init.precede(pf);</div>
</div><!-- fragment --><p>When <code>init</code> finishes, <code>pf</code> will see <code>first</code> pointing to the beginning of <code>vec</code> and <code>last</code> pointing to the end of <code>vec</code> and performs parallel iterations over the 1000 items.</p>
<p>A partitioner controls how the iteration space is divided among workers. Taskflow provides four partitioners, each suited to different workload characteristics:</p>
<ul>
<li><aclass="el" href="classtf_1_1StaticPartitioner.html" title="class to construct a static partitioner for scheduling parallel algorithms">tf::StaticPartitioner</a> divides the range into equal-sized chunks ahead of execution and assigns them to workers in order. It has the lowest scheduling overhead and delivers the best performance when every iteration costs roughly the same amount of work.</li>
<li><aclass="el" href="classtf_1_1DynamicPartitioner.html" title="class to create a dynamic partitioner for scheduling parallel algorithms">tf::DynamicPartitioner</a> distributes fixed-sized chunks to workers on demand as they become available. It adapts well to workloads where iteration cost varies, at the expense of slightly higher coordination overhead.</li>
<li><aclass="el" href="classtf_1_1GuidedPartitioner.html" title="class to create a guided partitioner for scheduling parallel algorithms">tf::GuidedPartitioner</a> distributes chunks whose size decreases adaptively as work is consumed — large chunks early to reduce overhead, smaller chunks late to balance the tail. This is the default partitioner and delivers stable, near-optimal performance across a wide range of workloads.</li>
<li><aclass="el" href="classtf_1_1RandomPartitioner.html" title="class to construct a random partitioner for scheduling parallel algorithms">tf::RandomPartitioner</a> distributes chunks of randomly sampled sizes, which can help avoid systematic load imbalances caused by data-dependent cost patterns.</li>
</ul>
<p>The following example creates two parallel-iteration tasks with different partitioners:</p>
<divclass="ttc" id="aclasstf_1_1GuidedPartitioner_html"><divclass="ttname"><ahref="classtf_1_1GuidedPartitioner.html">tf::GuidedPartitioner</a></div><divclass="ttdoc">class to create a guided partitioner for scheduling parallel algorithms</div><divclass="ttdef"><b>Definition</b> partitioner.hpp:636</div></div>
<divclass="ttc" id="aclasstf_1_1StaticPartitioner_html"><divclass="ttname"><ahref="classtf_1_1StaticPartitioner.html">tf::StaticPartitioner</a></div><divclass="ttdoc">class to construct a static partitioner for scheduling parallel algorithms</div><divclass="ttdef"><b>Definition</b> partitioner.hpp:476</div></div>
</div><!-- fragment --><p>As a rule of thumb, prefer <aclass="el" href="classtf_1_1StaticPartitioner.html" title="class to construct a static partitioner for scheduling parallel algorithms">tf::StaticPartitioner</a> for uniform workloads (e.g., element-wise arithmetic on arrays) and <aclass="el" href="classtf_1_1GuidedPartitioner.html" title="class to create a guided partitioner for scheduling parallel algorithms">tf::GuidedPartitioner</a> for irregular workloads (e.g., graph traversal, variable-length processing). <aclass="el" href="classtf_1_1DynamicPartitioner.html" title="class to create a dynamic partitioner for scheduling parallel algorithms">tf::DynamicPartitioner</a> is a good choice when chunks must be kept small and strictly equal in size.</p>
<dlclass="section note"><dt>Note</dt><dd>By default, parallel-iteration tasks use <aclass="el" href="namespacetf.html#ace2c5adcd5039483eebb6dbdbb6f33e3" title="default partitioner set to tf::GuidedPartitioner">tf::DefaultPartitioner</a> (currently <aclass="el" href="classtf_1_1GuidedPartitioner.html" title="class to create a guided partitioner for scheduling parallel algorithms">tf::GuidedPartitioner</a>) if no partitioner is specified. </dd></dl>
</div></div><!-- contents -->
</div><!-- PageDoc -->
</div><!-- doc-content -->
<!-- HTML footer for doxygen 1.13.1-->
<!-- start footer part -->
<divid="nav-path" class="navpath"><!-- id is needed for treeview function! -->