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