1 /*
2     Copyright (c) 2005-2023 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #ifndef __TBB_parallel_for_each_H
18 #define __TBB_parallel_for_each_H
19 
20 #include "detail/_config.h"
21 #include "detail/_namespace_injection.h"
22 #include "detail/_exception.h"
23 #include "detail/_task.h"
24 #include "detail/_aligned_space.h"
25 #include "detail/_small_object_pool.h"
26 #include "detail/_utils.h"
27 
28 #include "parallel_for.h"
29 #include "task_group.h" // task_group_context
30 
31 #include <iterator>
32 #include <type_traits>
33 
34 namespace tbb {
35 namespace detail {
36 #if __TBB_CPP20_CONCEPTS_PRESENT
37 namespace d1 {
38 template <typename Item>
39 class feeder;
40 
41 } // namespace d1
42 inline namespace d0 {
43 
44 template <typename Body, typename ItemType, typename FeederItemType>
45 concept parallel_for_each_body = std::invocable<const std::remove_reference_t<Body>&, ItemType&&> ||
46                                  std::invocable<const std::remove_reference_t<Body>&, ItemType&&, tbb::detail::d1::feeder<FeederItemType>&>;
47 
48 } // namespace d0
49 #endif // __TBB_CPP20_CONCEPTS_PRESENT
50 namespace d2 {
51 template<typename Body, typename Item> class feeder_impl;
52 } // namespace d2
53 
54 namespace d1 {
55 //! Class the user supplied algorithm body uses to add new tasks
56 template<typename Item>
57 class feeder {
58     feeder() {}
59     feeder(const feeder&) = delete;
60     void operator=( const feeder&) = delete;
61 
62     virtual ~feeder () {}
63     virtual void internal_add_copy(const Item& item) = 0;
64     virtual void internal_add_move(Item&& item) = 0;
65 
66     template<typename Body_, typename Item_> friend class d2::feeder_impl;
67 public:
68     //! Add a work item to a running parallel_for_each.
69     void add(const Item& item) {internal_add_copy(item);}
70     void add(Item&& item) {internal_add_move(std::move(item));}
71 };
72 
73 } // namespace d1
74 
75 namespace d2 {
76 using namespace tbb::detail::d1;
77 /** Selects one of the two possible forms of function call member operator.
78     @ingroup algorithms **/
79 template<class Body>
80 struct parallel_for_each_operator_selector {
81 public:
82     template<typename ItemArg, typename FeederArg>
83     static auto call(const Body& body, ItemArg&& item, FeederArg*)
84     -> decltype(tbb::detail::invoke(body, std::forward<ItemArg>(item)), void()) {
85         #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
86         // Suppression of Microsoft non-standard extension warnings
87         #pragma warning (push)
88         #pragma warning (disable: 4239)
89         #endif
90 
91         tbb::detail::invoke(body, std::forward<ItemArg>(item));
92 
93         #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
94         #pragma warning (pop)
95         #endif
96     }
97 
98     template<typename ItemArg, typename FeederArg>
99     static auto call(const Body& body, ItemArg&& item, FeederArg* feeder)
100     -> decltype(tbb::detail::invoke(body, std::forward<ItemArg>(item), *feeder), void()) {
101         #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
102         // Suppression of Microsoft non-standard extension warnings
103         #pragma warning (push)
104         #pragma warning (disable: 4239)
105         #endif
106         __TBB_ASSERT(feeder, "Feeder was not created but should be");
107 
108         tbb::detail::invoke(body, std::forward<ItemArg>(item), *feeder);
109 
110         #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
111         #pragma warning (pop)
112         #endif
113     }
114 };
115 
116 template<typename Body, typename Item>
117 struct feeder_item_task: public task {
118     using feeder_type = feeder_impl<Body, Item>;
119 
120     template <typename ItemType>
121     feeder_item_task(ItemType&& input_item, feeder_type& feeder, small_object_allocator& alloc) :
122         item(std::forward<ItemType>(input_item)),
123         my_feeder(feeder),
124         my_allocator(alloc)
125     {}
126 
127     void finalize(const execution_data& ed) {
128         my_feeder.my_wait_context.release();
129         my_allocator.delete_object(this, ed);
130     }
131 
132     //! Hack for resolve ambiguity between calls to the body with and without moving the stored copy
133     //! Executing body with moving the copy should have higher priority
134     using first_priority = int;
135     using second_priority = double;
136 
137     template <typename BodyType, typename ItemType, typename FeederType>
138     static auto call(const BodyType& call_body, ItemType& call_item, FeederType& call_feeder, first_priority)
139     -> decltype(parallel_for_each_operator_selector<Body>::call(call_body, std::move(call_item), &call_feeder), void())
140     {
141         parallel_for_each_operator_selector<Body>::call(call_body, std::move(call_item), &call_feeder);
142     }
143 
144     template <typename BodyType, typename ItemType, typename FeederType>
145     static void call(const BodyType& call_body, ItemType& call_item, FeederType& call_feeder, second_priority) {
146         parallel_for_each_operator_selector<Body>::call(call_body, call_item, &call_feeder);
147     }
148 
149     task* execute(execution_data& ed) override {
150         call(my_feeder.my_body, item, my_feeder, first_priority{});
151         finalize(ed);
152         return nullptr;
153     }
154 
155     task* cancel(execution_data& ed) override {
156         finalize(ed);
157         return nullptr;
158     }
159 
160     Item item;
161     feeder_type& my_feeder;
162     small_object_allocator my_allocator;
163 }; // class feeder_item_task
164 
165 /** Implements new task adding procedure.
166     @ingroup algorithms **/
167 template<typename Body, typename Item>
168 class feeder_impl : public feeder<Item> {
169     // Avoiding use of copy constructor in a virtual method if the type does not support it
170     void internal_add_copy_impl(std::true_type, const Item& item) {
171         using feeder_task = feeder_item_task<Body, Item>;
172         small_object_allocator alloc;
173         auto task = alloc.new_object<feeder_task>(item, *this, alloc);
174 
175         my_wait_context.reserve();
176         spawn(*task, my_execution_context);
177     }
178 
179     void internal_add_copy_impl(std::false_type, const Item&) {
180         __TBB_ASSERT(false, "Overloading for r-value reference doesn't work or it's not movable and not copyable object");
181     }
182 
183     void internal_add_copy(const Item& item) override {
184         internal_add_copy_impl(typename std::is_copy_constructible<Item>::type(), item);
185     }
186 
187     void internal_add_move(Item&& item) override {
188         using feeder_task = feeder_item_task<Body, Item>;
189         small_object_allocator alloc{};
190         auto task = alloc.new_object<feeder_task>(std::move(item), *this, alloc);
191 
192         my_wait_context.reserve();
193         spawn(*task, my_execution_context);
194     }
195 public:
196     feeder_impl(const Body& body, wait_context& w_context, task_group_context &context)
197       : my_body(body),
198         my_wait_context(w_context)
199       , my_execution_context(context)
200     {}
201 
202     const Body& my_body;
203     wait_context& my_wait_context;
204     task_group_context& my_execution_context;
205 }; // class feeder_impl
206 
207 /** Execute computation under one element of the range
208     @ingroup algorithms **/
209 template<typename Iterator, typename Body, typename Item>
210 struct for_each_iteration_task: public task {
211     using feeder_type = feeder_impl<Body, Item>;
212 
213     for_each_iteration_task(Iterator input_item_ptr, const Body& body, feeder_impl<Body, Item>* feeder_ptr, wait_context& wait_context) :
214         item_ptr(input_item_ptr), my_body(body), my_feeder_ptr(feeder_ptr), parent_wait_context(wait_context)
215     {}
216 
217     void finalize() {
218         parent_wait_context.release();
219     }
220 
221     task* execute(execution_data&) override {
222         parallel_for_each_operator_selector<Body>::call(my_body, *item_ptr, my_feeder_ptr);
223         finalize();
224         return nullptr;
225     }
226 
227     task* cancel(execution_data&) override {
228         finalize();
229         return nullptr;
230     }
231 
232     Iterator item_ptr;
233     const Body& my_body;
234     feeder_impl<Body, Item>* my_feeder_ptr;
235     wait_context& parent_wait_context;
236 }; // class for_each_iteration_task
237 
238 // Helper to get the type of the iterator to the internal sequence of copies
239 // If the element can be passed to the body as an rvalue - this iterator should be move_iterator
240 template <typename Body, typename Item, typename = void>
241 struct input_iteration_task_iterator_helper {
242     // For input iterators we pass const lvalue reference to the body
243     // It is prohibited to take non-constant lvalue references for input iterators
244     using type = const Item*;
245 };
246 
247 template <typename Body, typename Item>
248 struct input_iteration_task_iterator_helper<Body, Item,
249     tbb::detail::void_t<decltype(parallel_for_each_operator_selector<Body>::call(std::declval<const Body&>(),
250                                                                                  std::declval<Item&&>(),
251                                                                                  std::declval<feeder_impl<Body, Item>*>()))>>
252 {
253     using type = std::move_iterator<Item*>;
254 };
255 
256 /** Split one block task to several(max_block_size) iteration tasks for input iterators
257     @ingroup algorithms **/
258 template <typename Body, typename Item>
259 struct input_block_handling_task : public task {
260     static constexpr size_t max_block_size = 4;
261 
262     using feeder_type = feeder_impl<Body, Item>;
263     using iteration_task_iterator_type = typename input_iteration_task_iterator_helper<Body, Item>::type;
264     using iteration_task = for_each_iteration_task<iteration_task_iterator_type, Body, Item>;
265 
266     input_block_handling_task(wait_context& root_wait_context, task_group_context& e_context,
267                               const Body& body, feeder_impl<Body, Item>* feeder_ptr, small_object_allocator& alloc)
268         :my_size(0), my_wait_context(0), my_root_wait_context(root_wait_context),
269          my_execution_context(e_context), my_allocator(alloc)
270     {
271         auto item_it = block_iteration_space.begin();
272         for (auto* it = task_pool.begin(); it != task_pool.end(); ++it) {
273             new (it) iteration_task(iteration_task_iterator_type(item_it++), body, feeder_ptr, my_wait_context);
274         }
275     }
276 
277     void finalize(const execution_data& ed) {
278         my_root_wait_context.release();
279         my_allocator.delete_object(this, ed);
280     }
281 
282     task* execute(execution_data& ed) override {
283         __TBB_ASSERT( my_size > 0, "Negative size was passed to task");
284         for (std::size_t counter = 1; counter < my_size; ++counter) {
285             my_wait_context.reserve();
286             spawn(*(task_pool.begin() + counter), my_execution_context);
287         }
288         my_wait_context.reserve();
289         execute_and_wait(*task_pool.begin(), my_execution_context,
290                          my_wait_context,    my_execution_context);
291 
292         // deallocate current task after children execution
293         finalize(ed);
294         return nullptr;
295     }
296 
297     task* cancel(execution_data& ed) override {
298         finalize(ed);
299         return nullptr;
300     }
301 
302     ~input_block_handling_task() {
303         for(std::size_t counter = 0; counter < max_block_size; ++counter) {
304             (task_pool.begin() + counter)->~iteration_task();
305             if (counter < my_size) {
306                 (block_iteration_space.begin() + counter)->~Item();
307             }
308         }
309     }
310 
311     aligned_space<Item, max_block_size> block_iteration_space;
312     aligned_space<iteration_task, max_block_size> task_pool;
313     std::size_t my_size;
314     wait_context my_wait_context;
315     wait_context& my_root_wait_context;
316     task_group_context& my_execution_context;
317     small_object_allocator my_allocator;
318 }; // class input_block_handling_task
319 
320 /** Split one block task to several(max_block_size) iteration tasks for forward iterators
321     @ingroup algorithms **/
322 template <typename Iterator, typename Body, typename Item>
323 struct forward_block_handling_task : public task {
324     static constexpr size_t max_block_size = 4;
325 
326     using iteration_task = for_each_iteration_task<Iterator, Body, Item>;
327 
328     forward_block_handling_task(Iterator first, std::size_t size,
329                                 wait_context& w_context, task_group_context& e_context,
330                                 const Body& body, feeder_impl<Body, Item>* feeder_ptr,
331                                 small_object_allocator& alloc)
332         : my_size(size), my_wait_context(0), my_root_wait_context(w_context),
333           my_execution_context(e_context), my_allocator(alloc)
334     {
335         auto* task_it = task_pool.begin();
336         for (std::size_t i = 0; i < size; i++) {
337             new (task_it++) iteration_task(first, body, feeder_ptr, my_wait_context);
338             ++first;
339         }
340     }
341 
342     void finalize(const execution_data& ed) {
343         my_root_wait_context.release();
344         my_allocator.delete_object(this, ed);
345     }
346 
347     task* execute(execution_data& ed) override {
348         __TBB_ASSERT( my_size > 0, "Negative size was passed to task");
349         for(std::size_t counter = 1; counter < my_size; ++counter) {
350             my_wait_context.reserve();
351             spawn(*(task_pool.begin() + counter), my_execution_context);
352         }
353         my_wait_context.reserve();
354         execute_and_wait(*task_pool.begin(), my_execution_context,
355                          my_wait_context,    my_execution_context);
356 
357         // deallocate current task after children execution
358         finalize(ed);
359         return nullptr;
360     }
361 
362     task* cancel(execution_data& ed) override {
363         finalize(ed);
364         return nullptr;
365     }
366 
367     ~forward_block_handling_task() {
368         for(std::size_t counter = 0; counter < my_size; ++counter) {
369             (task_pool.begin() + counter)->~iteration_task();
370         }
371     }
372 
373     aligned_space<iteration_task, max_block_size> task_pool;
374     std::size_t my_size;
375     wait_context my_wait_context;
376     wait_context& my_root_wait_context;
377     task_group_context& my_execution_context;
378     small_object_allocator my_allocator;
379 }; // class forward_block_handling_task
380 
381 /** Body for parallel_for algorithm.
382   * Allows to redirect operations under random access iterators range to the parallel_for algorithm.
383     @ingroup algorithms **/
384 template <typename Iterator, typename Body, typename Item>
385 class parallel_for_body_wrapper {
386     Iterator my_first;
387     const Body& my_body;
388     feeder_impl<Body, Item>* my_feeder_ptr;
389 public:
390     parallel_for_body_wrapper(Iterator first, const Body& body, feeder_impl<Body, Item>* feeder_ptr)
391         : my_first(first), my_body(body), my_feeder_ptr(feeder_ptr) {}
392 
393     void operator()(tbb::blocked_range<std::size_t> range) const {
394 #if __INTEL_COMPILER
395 #pragma ivdep
396 #endif
397         for (std::size_t count = range.begin(); count != range.end(); count++) {
398             parallel_for_each_operator_selector<Body>::call(my_body, *(my_first + count),
399                                                             my_feeder_ptr);
400         }
401     }
402 }; // class parallel_for_body_wrapper
403 
404 
405 /** Helper for getting iterators tag including inherited custom tags
406     @ingroup algorithms */
407 template<typename It>
408 using tag = typename std::iterator_traits<It>::iterator_category;
409 
410 template<typename It>
411 using iterator_tag_dispatch = typename
412     std::conditional<
413         std::is_base_of<std::random_access_iterator_tag, tag<It>>::value,
414         std::random_access_iterator_tag,
415         typename std::conditional<
416             std::is_base_of<std::forward_iterator_tag, tag<It>>::value,
417             std::forward_iterator_tag,
418             std::input_iterator_tag
419         >::type
420     >::type;
421 
422 template <typename Body, typename Iterator, typename Item>
423 using feeder_is_required = tbb::detail::void_t<decltype(tbb::detail::invoke(std::declval<const Body>(),
424                                                                             std::declval<typename std::iterator_traits<Iterator>::reference>(),
425                                                                             std::declval<feeder<Item>&>()))>;
426 
427 // Creates feeder object only if the body can accept it
428 template <typename Iterator, typename Body, typename Item, typename = void>
429 struct feeder_holder {
430     feeder_holder( wait_context&, task_group_context&, const Body& ) {}
431 
432     feeder_impl<Body, Item>* feeder_ptr() { return nullptr; }
433 }; // class feeder_holder
434 
435 template <typename Iterator, typename Body, typename Item>
436 class feeder_holder<Iterator, Body, Item, feeder_is_required<Body, Iterator, Item>> {
437 public:
438     feeder_holder( wait_context& w_context, task_group_context& context, const Body& body )
439         : my_feeder(body, w_context, context) {}
440 
441     feeder_impl<Body, Item>* feeder_ptr() { return &my_feeder; }
442 private:
443     feeder_impl<Body, Item> my_feeder;
444 }; // class feeder_holder
445 
446 template <typename Iterator, typename Body, typename Item>
447 class for_each_root_task_base : public task {
448 public:
449     for_each_root_task_base(Iterator first, Iterator last, const Body& body, wait_context& w_context, task_group_context& e_context)
450         : my_first(first), my_last(last), my_wait_context(w_context), my_execution_context(e_context),
451           my_body(body), my_feeder_holder(my_wait_context, my_execution_context, my_body)
452     {
453         my_wait_context.reserve();
454     }
455 private:
456     task* cancel(execution_data&) override {
457         this->my_wait_context.release();
458         return nullptr;
459     }
460 protected:
461     Iterator my_first;
462     Iterator my_last;
463     wait_context& my_wait_context;
464     task_group_context& my_execution_context;
465     const Body& my_body;
466     feeder_holder<Iterator, Body, Item> my_feeder_holder;
467 }; // class for_each_root_task_base
468 
469 /** parallel_for_each algorithm root task - most generic version
470   * Splits input range to blocks
471     @ingroup algorithms **/
472 template <typename Iterator, typename Body, typename Item, typename IteratorTag = iterator_tag_dispatch<Iterator>>
473 class for_each_root_task : public for_each_root_task_base<Iterator, Body, Item>
474 {
475     using base_type = for_each_root_task_base<Iterator, Body, Item>;
476 public:
477     using base_type::base_type;
478 private:
479     task* execute(execution_data& ed) override {
480         using block_handling_type = input_block_handling_task<Body, Item>;
481 
482         if (this->my_first == this->my_last) {
483             this->my_wait_context.release();
484             return nullptr;
485         }
486 
487         this->my_wait_context.reserve();
488         small_object_allocator alloc{};
489         auto block_handling_task = alloc.new_object<block_handling_type>(ed, this->my_wait_context, this->my_execution_context,
490                                                                          this->my_body, this->my_feeder_holder.feeder_ptr(),
491                                                                          alloc);
492 
493         auto* block_iterator = block_handling_task->block_iteration_space.begin();
494         for (; !(this->my_first == this->my_last) && block_handling_task->my_size < block_handling_type::max_block_size; ++this->my_first) {
495             // Move semantics are automatically used when supported by the iterator
496             new (block_iterator++) Item(*this->my_first);
497             ++block_handling_task->my_size;
498         }
499 
500         // Do not access this after spawn to avoid races
501         spawn(*this, this->my_execution_context);
502         return block_handling_task;
503     }
504 }; // class for_each_root_task - most generic implementation
505 
506 /** parallel_for_each algorithm root task - forward iterator based specialization
507   * Splits input range to blocks
508     @ingroup algorithms **/
509 template <typename Iterator, typename Body, typename Item>
510 class for_each_root_task<Iterator, Body, Item, std::forward_iterator_tag>
511     : public for_each_root_task_base<Iterator, Body, Item>
512 {
513     using base_type = for_each_root_task_base<Iterator, Body, Item>;
514 public:
515     using base_type::base_type;
516 private:
517     task* execute(execution_data& ed) override {
518         using block_handling_type = forward_block_handling_task<Iterator, Body, Item>;
519         if (this->my_first == this->my_last) {
520             this->my_wait_context.release();
521             return nullptr;
522         }
523 
524         std::size_t block_size{0};
525         Iterator first_block_element = this->my_first;
526         for (; !(this->my_first == this->my_last) && block_size < block_handling_type::max_block_size; ++this->my_first) {
527             ++block_size;
528         }
529 
530         this->my_wait_context.reserve();
531         small_object_allocator alloc{};
532         auto block_handling_task = alloc.new_object<block_handling_type>(ed, first_block_element, block_size,
533                                                                          this->my_wait_context, this->my_execution_context,
534                                                                          this->my_body, this->my_feeder_holder.feeder_ptr(), alloc);
535 
536         // Do not access this after spawn to avoid races
537         spawn(*this, this->my_execution_context);
538         return block_handling_task;
539     }
540 }; // class for_each_root_task - forward iterator based specialization
541 
542 /** parallel_for_each algorithm root task - random access iterator based specialization
543   * Splits input range to blocks
544     @ingroup algorithms **/
545 template <typename Iterator, typename Body, typename Item>
546 class for_each_root_task<Iterator, Body, Item, std::random_access_iterator_tag>
547     : public for_each_root_task_base<Iterator, Body, Item>
548 {
549     using base_type = for_each_root_task_base<Iterator, Body, Item>;
550 public:
551     using base_type::base_type;
552 private:
553     task* execute(execution_data&) override {
554         tbb::parallel_for(
555             tbb::blocked_range<std::size_t>(0, std::distance(this->my_first, this->my_last)),
556             parallel_for_body_wrapper<Iterator, Body, Item>(this->my_first, this->my_body, this->my_feeder_holder.feeder_ptr())
557             , this->my_execution_context
558         );
559 
560         this->my_wait_context.release();
561         return nullptr;
562     }
563 }; // class for_each_root_task - random access iterator based specialization
564 
565 /** Helper for getting item type. If item type can be deduced from feeder - got it from feeder,
566     if feeder is generic - got item type from range.
567     @ingroup algorithms */
568 template<typename Body, typename Item, typename FeederArg>
569 auto feeder_argument_parser(void (Body::*)(Item, feeder<FeederArg>&) const) -> FeederArg;
570 
571 template<typename Body, typename>
572 decltype(feeder_argument_parser<Body>(&Body::operator())) get_item_type_impl(int); // for (T, feeder<T>)
573 template<typename Body, typename Item> Item get_item_type_impl(...); // stub
574 
575 template <typename Body, typename Item>
576 using get_item_type = decltype(get_item_type_impl<Body, Item>(0));
577 
578 #if __TBB_CPP20_CONCEPTS_PRESENT
579 template <typename Body, typename ItemType>
580 using feeder_item_type = std::remove_cvref_t<get_item_type<Body, ItemType>>;
581 
582 template <typename Body, typename Iterator>
583 concept parallel_for_each_iterator_body =
584     parallel_for_each_body<Body, iterator_reference_type<Iterator>, feeder_item_type<Body, iterator_reference_type<Iterator>>>;
585 
586 template <typename Body, typename Range>
587 concept parallel_for_each_range_body =
588     parallel_for_each_body<Body, range_reference_type<Range>, feeder_item_type<Body, range_reference_type<Range>>>;
589 #endif
590 
591 /** Implements parallel iteration over a range.
592     @ingroup algorithms */
593 template<typename Iterator, typename Body>
594 void run_parallel_for_each( Iterator first, Iterator last, const Body& body, task_group_context& context)
595 {
596     if (!(first == last)) {
597         using ItemType = get_item_type<Body, typename std::iterator_traits<Iterator>::value_type>;
598         wait_context w_context(0);
599 
600         for_each_root_task<Iterator, Body, ItemType> root_task(first, last, body, w_context, context);
601 
602         execute_and_wait(root_task, context, w_context, context);
603     }
604 }
605 
606 /** \page parallel_for_each_body_req Requirements on parallel_for_each body
607     Class \c Body implementing the concept of parallel_for_each body must define:
608     - \code
609         B::operator()(
610                 cv_item_type item,
611                 feeder<item_type>& feeder
612         ) const
613 
614         OR
615 
616         B::operator()( cv_item_type& item ) const
617       \endcode                                               Process item.
618                                                              May be invoked concurrently  for the same \c this but different \c item.
619 
620     - \code item_type( const item_type& ) \endcode
621                                                              Copy a work item.
622     - \code ~item_type() \endcode                            Destroy a work item
623 **/
624 
625 /** \name parallel_for_each
626     See also requirements on \ref parallel_for_each_body_req "parallel_for_each Body". **/
627 //@{
628 //! Parallel iteration over a range, with optional addition of more work.
629 /** @ingroup algorithms */
630 template<typename Iterator, typename Body>
631     __TBB_requires(std::input_iterator<Iterator> && parallel_for_each_iterator_body<Body, Iterator>)
632 void parallel_for_each(Iterator first, Iterator last, const Body& body) {
633     task_group_context context(PARALLEL_FOR_EACH);
634     run_parallel_for_each<Iterator, Body>(first, last, body, context);
635 }
636 
637 template<typename Range, typename Body>
638     __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>)
639 void parallel_for_each(Range& rng, const Body& body) {
640     parallel_for_each(std::begin(rng), std::end(rng), body);
641 }
642 
643 template<typename Range, typename Body>
644     __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>)
645 void parallel_for_each(const Range& rng, const Body& body) {
646     parallel_for_each(std::begin(rng), std::end(rng), body);
647 }
648 
649 //! Parallel iteration over a range, with optional addition of more work and user-supplied context
650 /** @ingroup algorithms */
651 template<typename Iterator, typename Body>
652     __TBB_requires(std::input_iterator<Iterator> && parallel_for_each_iterator_body<Body, Iterator>)
653 void parallel_for_each(Iterator first, Iterator last, const Body& body, task_group_context& context) {
654     run_parallel_for_each<Iterator, Body>(first, last, body, context);
655 }
656 
657 template<typename Range, typename Body>
658     __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>)
659 void parallel_for_each(Range& rng, const Body& body, task_group_context& context) {
660     parallel_for_each(std::begin(rng), std::end(rng), body, context);
661 }
662 
663 template<typename Range, typename Body>
664     __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>)
665 void parallel_for_each(const Range& rng, const Body& body, task_group_context& context) {
666     parallel_for_each(std::begin(rng), std::end(rng), body, context);
667 }
668 
669 } // namespace d2
670 } // namespace detail
671 //! @endcond
672 //@}
673 
674 inline namespace v1 {
675 using detail::d2::parallel_for_each;
676 using detail::d1::feeder;
677 } // namespace v1
678 
679 } // namespace tbb
680 
681 #endif /* __TBB_parallel_for_each_H */
682