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