1 /* 2 Copyright (c) 2005-2021 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 (push) 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 (push) 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 (block_iteration_space.begin() + counter)->~Item(); 310 } 311 } 312 313 aligned_space<Item, max_block_size> block_iteration_space; 314 aligned_space<iteration_task, max_block_size> task_pool; 315 std::size_t my_size; 316 wait_context my_wait_context; 317 wait_context& my_root_wait_context; 318 task_group_context& my_execution_context; 319 small_object_allocator my_allocator; 320 }; // class input_block_handling_task 321 322 /** Split one block task to several(max_block_size) iteration tasks for forward iterators 323 @ingroup algorithms **/ 324 template <typename Iterator, typename Body, typename Item> 325 struct forward_block_handling_task : public task { 326 static constexpr size_t max_block_size = 4; 327 328 using iteration_task = for_each_iteration_task<Iterator, Body, Item>; 329 330 forward_block_handling_task(Iterator first, std::size_t size, 331 wait_context& w_context, task_group_context& e_context, 332 const Body& body, feeder_impl<Body, Item>* feeder_ptr, 333 small_object_allocator& alloc) 334 : my_size(size), my_wait_context(0), my_root_wait_context(w_context), 335 my_execution_context(e_context), my_allocator(alloc) 336 { 337 auto* task_it = task_pool.begin(); 338 for (std::size_t i = 0; i < size; i++) { 339 new (task_it++) iteration_task(first, body, feeder_ptr, my_wait_context); 340 ++first; 341 } 342 } 343 344 void finalize(const execution_data& ed) { 345 my_root_wait_context.release(); 346 my_allocator.delete_object(this, ed); 347 } 348 349 task* execute(execution_data& ed) override { 350 __TBB_ASSERT( my_size > 0, "Negative size was passed to task"); 351 for(std::size_t counter = 1; counter < my_size; ++counter) { 352 my_wait_context.reserve(); 353 spawn(*(task_pool.begin() + counter), my_execution_context); 354 } 355 my_wait_context.reserve(); 356 execute_and_wait(*task_pool.begin(), my_execution_context, 357 my_wait_context, my_execution_context); 358 359 // deallocate current task after children execution 360 finalize(ed); 361 return nullptr; 362 } 363 364 task* cancel(execution_data& ed) override { 365 finalize(ed); 366 return nullptr; 367 } 368 369 ~forward_block_handling_task() { 370 for(std::size_t counter = 0; counter < my_size; ++counter) { 371 (task_pool.begin() + counter)->~iteration_task(); 372 } 373 } 374 375 aligned_space<iteration_task, max_block_size> task_pool; 376 std::size_t my_size; 377 wait_context my_wait_context; 378 wait_context& my_root_wait_context; 379 task_group_context& my_execution_context; 380 small_object_allocator my_allocator; 381 }; // class forward_block_handling_task 382 383 /** Body for parallel_for algorithm. 384 * Allows to redirect operations under random access iterators range to the parallel_for algorithm. 385 @ingroup algorithms **/ 386 template <typename Iterator, typename Body, typename Item> 387 class parallel_for_body_wrapper { 388 Iterator my_first; 389 const Body& my_body; 390 feeder_impl<Body, Item>* my_feeder_ptr; 391 public: 392 parallel_for_body_wrapper(Iterator first, const Body& body, feeder_impl<Body, Item>* feeder_ptr) 393 : my_first(first), my_body(body), my_feeder_ptr(feeder_ptr) {} 394 395 void operator()(tbb::blocked_range<std::size_t> range) const { 396 #if __INTEL_COMPILER 397 #pragma ivdep 398 #endif 399 for (std::size_t count = range.begin(); count != range.end(); count++) { 400 parallel_for_each_operator_selector<Body>::call(my_body, *(my_first + count), 401 my_feeder_ptr); 402 } 403 } 404 }; // class parallel_for_body_wrapper 405 406 407 /** Helper for getting iterators tag including inherited custom tags 408 @ingroup algorithms */ 409 template<typename It> 410 using tag = typename std::iterator_traits<It>::iterator_category; 411 412 template<typename It> 413 using iterator_tag_dispatch = typename 414 std::conditional< 415 std::is_base_of<std::random_access_iterator_tag, tag<It>>::value, 416 std::random_access_iterator_tag, 417 typename std::conditional< 418 std::is_base_of<std::forward_iterator_tag, tag<It>>::value, 419 std::forward_iterator_tag, 420 std::input_iterator_tag 421 >::type 422 >::type; 423 424 template <typename Body, typename Iterator, typename Item> 425 using feeder_is_required = tbb::detail::void_t<decltype(std::declval<const Body>()(std::declval<typename std::iterator_traits<Iterator>::reference>(), 426 std::declval<feeder<Item>&>()))>; 427 428 // Creates feeder object only if the body can accept it 429 template <typename Iterator, typename Body, typename Item, typename = void> 430 struct feeder_holder { 431 feeder_holder( wait_context&, task_group_context&, const Body& ) {} 432 433 feeder_impl<Body, Item>* feeder_ptr() { return nullptr; } 434 }; // class feeder_holder 435 436 template <typename Iterator, typename Body, typename Item> 437 class feeder_holder<Iterator, Body, Item, feeder_is_required<Body, Iterator, Item>> { 438 public: 439 feeder_holder( wait_context& w_context, task_group_context& context, const Body& body ) 440 : my_feeder(body, w_context, context) {} 441 442 feeder_impl<Body, Item>* feeder_ptr() { return &my_feeder; } 443 private: 444 feeder_impl<Body, Item> my_feeder; 445 }; // class feeder_holder 446 447 template <typename Iterator, typename Body, typename Item> 448 class for_each_root_task_base : public task { 449 public: 450 for_each_root_task_base(Iterator first, Iterator last, const Body& body, wait_context& w_context, task_group_context& e_context) 451 : my_first(first), my_last(last), my_wait_context(w_context), my_execution_context(e_context), 452 my_body(body), my_feeder_holder(my_wait_context, my_execution_context, my_body) 453 { 454 my_wait_context.reserve(); 455 } 456 private: 457 task* cancel(execution_data&) override { 458 this->my_wait_context.release(); 459 return nullptr; 460 } 461 protected: 462 Iterator my_first; 463 Iterator my_last; 464 wait_context& my_wait_context; 465 task_group_context& my_execution_context; 466 const Body& my_body; 467 feeder_holder<Iterator, Body, Item> my_feeder_holder; 468 }; // class for_each_root_task_base 469 470 /** parallel_for_each algorithm root task - most generic version 471 * Splits input range to blocks 472 @ingroup algorithms **/ 473 template <typename Iterator, typename Body, typename Item, typename IteratorTag = iterator_tag_dispatch<Iterator>> 474 class for_each_root_task : public for_each_root_task_base<Iterator, Body, Item> 475 { 476 using base_type = for_each_root_task_base<Iterator, Body, Item>; 477 public: 478 using base_type::base_type; 479 private: 480 task* execute(execution_data& ed) override { 481 using block_handling_type = input_block_handling_task<Body, Item>; 482 483 if (this->my_first == this->my_last) { 484 this->my_wait_context.release(); 485 return nullptr; 486 } 487 488 this->my_wait_context.reserve(); 489 small_object_allocator alloc{}; 490 auto block_handling_task = alloc.new_object<block_handling_type>(ed, this->my_wait_context, this->my_execution_context, 491 this->my_body, this->my_feeder_holder.feeder_ptr(), 492 alloc); 493 494 auto* block_iterator = block_handling_task->block_iteration_space.begin(); 495 for (; !(this->my_first == this->my_last) && block_handling_task->my_size < block_handling_type::max_block_size; ++this->my_first) { 496 // Move semantics are automatically used when supported by the iterator 497 new (block_iterator++) Item(*this->my_first); 498 ++block_handling_task->my_size; 499 } 500 501 // Do not access this after spawn to avoid races 502 spawn(*this, this->my_execution_context); 503 return block_handling_task; 504 } 505 }; // class for_each_root_task - most generic implementation 506 507 /** parallel_for_each algorithm root task - forward iterator based specialization 508 * Splits input range to blocks 509 @ingroup algorithms **/ 510 template <typename Iterator, typename Body, typename Item> 511 class for_each_root_task<Iterator, Body, Item, std::forward_iterator_tag> 512 : public for_each_root_task_base<Iterator, Body, Item> 513 { 514 using base_type = for_each_root_task_base<Iterator, Body, Item>; 515 public: 516 using base_type::base_type; 517 private: 518 task* execute(execution_data& ed) override { 519 using block_handling_type = forward_block_handling_task<Iterator, Body, Item>; 520 if (this->my_first == this->my_last) { 521 this->my_wait_context.release(); 522 return nullptr; 523 } 524 525 std::size_t block_size{0}; 526 Iterator first_block_element = this->my_first; 527 for (; !(this->my_first == this->my_last) && block_size < block_handling_type::max_block_size; ++this->my_first) { 528 ++block_size; 529 } 530 531 this->my_wait_context.reserve(); 532 small_object_allocator alloc{}; 533 auto block_handling_task = alloc.new_object<block_handling_type>(ed, first_block_element, block_size, 534 this->my_wait_context, this->my_execution_context, 535 this->my_body, this->my_feeder_holder.feeder_ptr(), alloc); 536 537 // Do not access this after spawn to avoid races 538 spawn(*this, this->my_execution_context); 539 return block_handling_task; 540 } 541 }; // class for_each_root_task - forward iterator based specialization 542 543 /** parallel_for_each algorithm root task - random access iterator based specialization 544 * Splits input range to blocks 545 @ingroup algorithms **/ 546 template <typename Iterator, typename Body, typename Item> 547 class for_each_root_task<Iterator, Body, Item, std::random_access_iterator_tag> 548 : public for_each_root_task_base<Iterator, Body, Item> 549 { 550 using base_type = for_each_root_task_base<Iterator, Body, Item>; 551 public: 552 using base_type::base_type; 553 private: 554 task* execute(execution_data&) override { 555 tbb::parallel_for( 556 tbb::blocked_range<std::size_t>(0, std::distance(this->my_first, this->my_last)), 557 parallel_for_body_wrapper<Iterator, Body, Item>(this->my_first, this->my_body, this->my_feeder_holder.feeder_ptr()) 558 , this->my_execution_context 559 ); 560 561 this->my_wait_context.release(); 562 return nullptr; 563 } 564 }; // class for_each_root_task - random access iterator based specialization 565 566 /** Helper for getting item type. If item type can be deduced from feeder - got it from feeder, 567 if feeder is generic - got item type from range. 568 @ingroup algorithms */ 569 template<typename Body, typename Item, typename FeederArg> 570 auto feeder_argument_parser(void (Body::*)(Item, feeder<FeederArg>&) const) -> FeederArg; 571 572 template<typename Body, typename> 573 decltype(feeder_argument_parser<Body>(&Body::operator())) get_item_type_impl(int); // for (T, feeder<T>) 574 template<typename Body, typename Item> Item get_item_type_impl(...); // stub 575 576 template <typename Body, typename Item> 577 using get_item_type = decltype(get_item_type_impl<Body, Item>(0)); 578 579 #if __TBB_CPP20_CONCEPTS_PRESENT 580 template <typename Body, typename ItemType> 581 using feeder_item_type = std::remove_cvref_t<get_item_type<Body, ItemType>>; 582 583 template <typename Body, typename Iterator> 584 concept parallel_for_each_iterator_body = 585 parallel_for_each_body<Body, iterator_reference_type<Iterator>, feeder_item_type<Body, iterator_reference_type<Iterator>>>; 586 587 template <typename Body, typename Range> 588 concept parallel_for_each_range_body = 589 parallel_for_each_body<Body, range_reference_type<Range>, feeder_item_type<Body, range_reference_type<Range>>>; 590 #endif 591 592 /** Implements parallel iteration over a range. 593 @ingroup algorithms */ 594 template<typename Iterator, typename Body> 595 void run_parallel_for_each( Iterator first, Iterator last, const Body& body, task_group_context& context) 596 { 597 if (!(first == last)) { 598 using ItemType = get_item_type<Body, typename std::iterator_traits<Iterator>::value_type>; 599 wait_context w_context(0); 600 601 for_each_root_task<Iterator, Body, ItemType> root_task(first, last, body, w_context, context); 602 603 execute_and_wait(root_task, context, w_context, context); 604 } 605 } 606 607 /** \page parallel_for_each_body_req Requirements on parallel_for_each body 608 Class \c Body implementing the concept of parallel_for_each body must define: 609 - \code 610 B::operator()( 611 cv_item_type item, 612 feeder<item_type>& feeder 613 ) const 614 615 OR 616 617 B::operator()( cv_item_type& item ) const 618 \endcode Process item. 619 May be invoked concurrently for the same \c this but different \c item. 620 621 - \code item_type( const item_type& ) \endcode 622 Copy a work item. 623 - \code ~item_type() \endcode Destroy a work item 624 **/ 625 626 /** \name parallel_for_each 627 See also requirements on \ref parallel_for_each_body_req "parallel_for_each Body". **/ 628 //@{ 629 //! Parallel iteration over a range, with optional addition of more work. 630 /** @ingroup algorithms */ 631 template<typename Iterator, typename Body> 632 __TBB_requires(std::input_iterator<Iterator> && parallel_for_each_iterator_body<Body, Iterator>) 633 void parallel_for_each(Iterator first, Iterator last, const Body& body) { 634 task_group_context context(PARALLEL_FOR_EACH); 635 run_parallel_for_each<Iterator, Body>(first, last, body, context); 636 } 637 638 template<typename Range, typename Body> 639 __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>) 640 void parallel_for_each(Range& rng, const Body& body) { 641 parallel_for_each(std::begin(rng), std::end(rng), body); 642 } 643 644 template<typename Range, typename Body> 645 __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>) 646 void parallel_for_each(const Range& rng, const Body& body) { 647 parallel_for_each(std::begin(rng), std::end(rng), body); 648 } 649 650 //! Parallel iteration over a range, with optional addition of more work and user-supplied context 651 /** @ingroup algorithms */ 652 template<typename Iterator, typename Body> 653 __TBB_requires(std::input_iterator<Iterator> && parallel_for_each_iterator_body<Body, Iterator>) 654 void parallel_for_each(Iterator first, Iterator last, const Body& body, task_group_context& context) { 655 run_parallel_for_each<Iterator, Body>(first, last, body, context); 656 } 657 658 template<typename Range, typename Body> 659 __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>) 660 void parallel_for_each(Range& rng, const Body& body, task_group_context& context) { 661 parallel_for_each(std::begin(rng), std::end(rng), body, context); 662 } 663 664 template<typename Range, typename Body> 665 __TBB_requires(container_based_sequence<Range, std::input_iterator_tag> && parallel_for_each_range_body<Body, Range>) 666 void parallel_for_each(const Range& rng, const Body& body, task_group_context& context) { 667 parallel_for_each(std::begin(rng), std::end(rng), body, context); 668 } 669 670 } // namespace d2 671 } // namespace detail 672 //! @endcond 673 //@} 674 675 inline namespace v1 { 676 using detail::d2::parallel_for_each; 677 using detail::d1::feeder; 678 } // namespace v1 679 680 } // namespace tbb 681 682 #endif /* __TBB_parallel_for_each_H */ 683