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