1[/ 2 Copyright Oliver Kowalke 2009. 3 Distributed under the Boost Software License, Version 1.0. 4 (See accompanying file LICENSE_1_0.txt or copy at 5 http://www.boost.org/LICENSE_1_0.txt 6] 7 8[section:asymmetric Asymmetric coroutine] 9 10Two asymmetric coroutine types - __push_coro__ and __pull_coro__ - provide a 11unidirectional transfer of data. 12 13 14[heading __pull_coro__] 15__pull_coro__ transfers data from another execution context (== pulled-from). 16The template parameter defines the transferred parameter type. 17The constructor of __pull_coro__ takes a function (__coro_fn__) accepting a 18reference to an __push_coro__ as argument. Instantiating an __pull_coro__ passes 19the control of execution to __coro_fn__ and a complementary __push_coro__ is 20synthesized by the library and passed as reference to __coro_fn__. 21 22This kind of coroutine provides __pull_coro_op__. This method only switches 23context; it transfers no data. 24 25__pull_coro__ provides input iterators (__pull_coro_it__) and __begin__/__end__ 26are overloaded. The increment-operation switches the context and transfers data. 27 28 boost::coroutines::asymmetric_coroutine<int>::pull_type source( 29 [&](boost::coroutines::asymmetric_coroutine<int>::push_type& sink){ 30 int first=1,second=1; 31 sink(first); 32 sink(second); 33 for(int i=0;i<8;++i){ 34 int third=first+second; 35 first=second; 36 second=third; 37 sink(third); 38 } 39 }); 40 41 for(auto i:source) 42 std::cout << i << " "; 43 44 output: 45 1 1 2 3 5 8 13 21 34 55 46 47In this example an __pull_coro__ is created in the main execution context taking 48a lambda function (== __coro_fn__) which calculates Fibonacci numbers in a 49simple ['for]-loop. 50The __coro_fn__ is executed in a newly created execution context which is 51managed by the instance of __pull_coro__. 52An __push_coro__ is automatically generated by the library and passed as 53reference to the lambda function. Each time the lambda function calls 54__push_coro_op__ with another Fibonacci number, __push_coro__ transfers it back 55to the main execution context. The local state of __coro_fn__ is preserved and 56will be restored upon transferring execution control back to __coro_fn__ 57to calculate the next Fibonacci number. 58Because __pull_coro__ provides input iterators and __begin__/__end__ are 59overloaded, a ['range-based for]-loop can be used to iterate over the generated 60Fibonacci numbers. 61 62 63[heading __push_coro__] 64__push_coro__ transfers data to the other execution context (== pushed-to). 65The template parameter defines the transferred parameter type. 66The constructor of __push_coro__ takes a function (__coro_fn__) accepting a 67reference to an __pull_coro__ as argument. In contrast to __pull_coro__, 68instantiating an __push_coro__ does not pass the control of execution to 69__coro_fn__ - instead the first call of __push_coro_op__ synthesizes a 70complementary __pull_coro__ and passes it as reference to __coro_fn__. 71 72The __push_coro__ interface does not contain a ['get()]-function: you can not retrieve 73values from another execution context with this kind of coroutine. 74 75__push_coro__ provides output iterators (__push_coro_it__) and 76__begin__/__end__ are overloaded. The increment-operation switches the context 77and transfers data. 78 79 struct FinalEOL{ 80 ~FinalEOL(){ 81 std::cout << std::endl; 82 } 83 }; 84 85 const int num=5, width=15; 86 boost::coroutines::asymmetric_coroutine<std::string>::push_type writer( 87 [&](boost::coroutines::asymmetric_coroutine<std::string>::pull_type& in){ 88 // finish the last line when we leave by whatever means 89 FinalEOL eol; 90 // pull values from upstream, lay them out 'num' to a line 91 for (;;){ 92 for(int i=0;i<num;++i){ 93 // when we exhaust the input, stop 94 if(!in) return; 95 std::cout << std::setw(width) << in.get(); 96 // now that we've handled this item, advance to next 97 in(); 98 } 99 // after 'num' items, line break 100 std::cout << std::endl; 101 } 102 }); 103 104 std::vector<std::string> words{ 105 "peas", "porridge", "hot", "peas", 106 "porridge", "cold", "peas", "porridge", 107 "in", "the", "pot", "nine", 108 "days", "old" }; 109 110 std::copy(boost::begin(words),boost::end(words),boost::begin(writer)); 111 112 output: 113 peas porridge hot peas porridge 114 cold peas porridge in the 115 pot nine days old 116 117In this example an __push_coro__ is created in the main execution context 118accepting a lambda function (== __coro_fn__) which requests strings and lays out 119'num' of them on each line. 120This demonstrates the inversion of control permitted by coroutines. Without 121coroutines, a utility function to perform the same job would necessarily 122accept each new value as a function parameter, returning after processing that 123single value. That function would depend on a static state variable. A 124__coro_fn__, however, can request each new value as if by calling a function 125-- even though its caller also passes values as if by calling a function. 126The __coro_fn__ is executed in a newly created execution context which is 127managed by the instance of __push_coro__. 128The main execution context passes the strings to the __coro_fn__ by calling 129__push_coro_op__. 130An __pull_coro__ instance is automatically generated by the library and passed as 131reference to the lambda function. The __coro_fn__ accesses the strings passed 132from the main execution context by calling __pull_coro_get__ and lays those 133strings out on ['std::cout] according the parameters 'num' and 'width'. 134The local state of __coro_fn__ is preserved and will be restored after 135transferring execution control back to __coro_fn__. 136Because __push_coro__ provides output iterators and __begin__/__end__ are 137overloaded, the ['std::copy] algorithm can be used to iterate over the vector 138containing the strings and pass them one by one to the coroutine. 139 140 141[heading coroutine-function] 142The __coro_fn__ returns ['void] and takes its counterpart-coroutine as 143argument, so that using the coroutine passed as argument to __coro_fn__ is the 144only way to transfer data and execution control back to the caller. 145Both coroutine types take the same template argument. 146For __pull_coro__ the __coro_fn__ is entered at __pull_coro__ construction. 147For __push_coro__ the __coro_fn__ is not entered at __push_coro__ construction 148but entered by the first invocation of __push_coro_op__. 149After execution control is returned from __coro_fn__ the state of the 150coroutine can be checked via __pull_coro_bool__ returning `true` if the 151coroutine is still valid (__coro_fn__ has not terminated). Unless the first 152template parameter is `void`, `true` also implies that a data value is 153available. 154 155 156[heading passing data from a pull-coroutine to main-context] 157In order to transfer data from an __pull_coro__ to the main-context the framework 158synthesizes an __push_coro__ associated with the __pull_coro__ instance in the 159main-context. The synthesized __push_coro__ is passed as argument to __coro_fn__. 160The __coro_fn__ must call this __push_coro_op__ in order to transfer each 161data value back to the main-context. 162In the main-context, the __pull_coro_bool__ determines whether the coroutine is 163still valid and a data value is available or __coro_fn__ has terminated 164(__pull_coro__ is invalid; no data value available). Access to the transferred 165data value is given by __pull_coro_get__. 166 167 boost::coroutines::asymmetric_coroutine<int>::pull_type source( // constructor enters coroutine-function 168 [&](boost::coroutines::asymmetric_coroutine<int>::push_type& sink){ 169 sink(1); // push {1} back to main-context 170 sink(1); // push {1} back to main-context 171 sink(2); // push {2} back to main-context 172 sink(3); // push {3} back to main-context 173 sink(5); // push {5} back to main-context 174 sink(8); // push {8} back to main-context 175 }); 176 177 while(source){ // test if pull-coroutine is valid 178 int ret=source.get(); // access data value 179 source(); // context-switch to coroutine-function 180 } 181 182 183[heading passing data from main-context to a push-coroutine] 184In order to transfer data to an __push_coro__ from the main-context the framework 185synthesizes an __pull_coro__ associated with the __push_coro__ instance in the 186main-context. The synthesized __pull_coro__ is passed as argument to __coro_fn__. 187The main-context must call this __push_coro_op__ in order to transfer each data 188value into the __coro_fn__. 189Access to the transferred data value is given by __pull_coro_get__. 190 191 boost::coroutines::asymmetric_coroutine<int>::push_type sink( // constructor does NOT enter coroutine-function 192 [&](boost::coroutines::asymmetric_coroutine<int>::pull_type& source){ 193 for (int i:source) { 194 std::cout << i << " "; 195 } 196 }); 197 198 std::vector<int> v{1,1,2,3,5,8,13,21,34,55}; 199 for( int i:v){ 200 sink(i); // push {i} to coroutine-function 201 } 202 203 204[heading accessing parameters] 205Parameters returned from or transferred to the __coro_fn__ can be accessed with 206__pull_coro_get__. 207 208Splitting-up the access of parameters from context switch function enables to 209check if __pull_coro__ is valid after return from __pull_coro_op__, e.g. 210__pull_coro__ has values and __coro_fn__ has not terminated. 211 212 boost::coroutines::asymmetric_coroutine<boost::tuple<int,int>>::push_type sink( 213 [&](boost::coroutines::asymmetric_coroutine<boost::tuple<int,int>>::pull_type& source){ 214 // access tuple {7,11}; x==7 y==1 215 int x,y; 216 boost::tie(x,y)=source.get(); 217 }); 218 219 sink(boost::make_tuple(7,11)); 220 221 222[heading exceptions] 223An exception thrown inside an __pull_coro__'s __coro_fn__ before its first call 224to __push_coro_op__ will be re-thrown by the __pull_coro__ constructor. After an 225__pull_coro__'s __coro_fn__'s first call to __push_coro_op__, any subsequent 226exception inside that __coro_fn__ will be re-thrown by __pull_coro_op__. 227__pull_coro_get__ does not throw. 228 229An exception thrown inside an __push_coro__'s __coro_fn__ will be re-thrown by 230__push_coro_op__. 231 232[important Code executed by __coro_fn__ must not prevent the propagation of the 233__forced_unwind__ exception. Absorbing that exception will cause stack 234unwinding to fail. Thus, any code that catches all exceptions must re-throw any 235pending __forced_unwind__ exception.] 236 237 try { 238 // code that might throw 239 } catch(const boost::coroutines::detail::forced_unwind&) { 240 throw; 241 } catch(...) { 242 // possibly not re-throw pending exception 243 } 244 245[important Do not jump from inside a catch block and then re-throw the 246exception in another execution context.] 247 248 249[heading Stack unwinding] 250Sometimes it is necessary to unwind the stack of an unfinished coroutine to 251destroy local stack variables so they can release allocated resources (RAII 252pattern). The `attributes` argument of the coroutine constructor 253indicates whether the destructor should unwind the stack (stack is unwound by 254default). 255 256Stack unwinding assumes the following preconditions: 257 258* The coroutine is not __not_a_coro__ 259* The coroutine is not complete 260* The coroutine is not running 261* The coroutine owns a stack 262 263After unwinding, a __coro__ is complete. 264 265 struct X { 266 X(){ 267 std::cout<<"X()"<<std::endl; 268 } 269 270 ~X(){ 271 std::cout<<"~X()"<<std::endl; 272 } 273 }; 274 275 { 276 boost::coroutines::asymmetric_coroutine<void>::push_type sink( 277 [&](boost::coroutines::asymmetric_coroutine<void>::pull_type& source){ 278 X x; 279 for(int=0;;++i){ 280 std::cout<<"fn(): "<<i<<std::endl; 281 // transfer execution control back to main() 282 source(); 283 } 284 }); 285 286 sink(); 287 sink(); 288 sink(); 289 sink(); 290 sink(); 291 292 std::cout<<"sink is complete: "<<std::boolalpha<<!sink<<"\n"; 293 } 294 295 output: 296 X() 297 fn(): 0 298 fn(): 1 299 fn(): 2 300 fn(): 3 301 fn(): 4 302 fn(): 5 303 sink is complete: false 304 ~X() 305 306 307[heading Range iterators] 308__boost_coroutine__ provides output- and input-iterators using __boost_range__. 309__pull_coro__ can be used via input-iterators using __begin__ and __end__. 310 311 int number=2,exponent=8; 312 boost::coroutines::asymmetric_coroutine< int >::pull_type source( 313 [&]( boost::coroutines::asymmetric_coroutine< int >::push_type & sink){ 314 int counter=0,result=1; 315 while(counter++<exponent){ 316 result=result*number; 317 sink(result); 318 } 319 }); 320 321 for (auto i:source) 322 std::cout << i << " "; 323 324 output: 325 2 4 8 16 32 64 128 256 326 327['asymmetric_coroutine<>::pull_type::iterator::operator++()] corresponds to 328__pull_coro_op__; ['asymmetric_coroutine<>::pull_type::iterator::operator*()] 329roughly corresponds to __pull_coro_get__. An iterator originally obtained from 330__begin__ of an __pull_coro__ compares equal to an iterator obtained from 331__end__ of that same __pull_coro__ instance when its __pull_coro_bool__ would 332return `false`]. 333 334[note If `T` is a move-only type, then 335['asymmetric_coroutine<T>::pull_type::iterator] may only be dereferenced once 336before it is incremented again.] 337 338Output-iterators can be created from __push_coro__. 339 340 boost::coroutines::asymmetric_coroutine<int>::push_type sink( 341 [&](boost::coroutines::asymmetric_coroutine<int>::pull_type& source){ 342 while(source){ 343 std::cout << source.get() << " "; 344 source(); 345 } 346 }); 347 348 std::vector<int> v{1,1,2,3,5,8,13,21,34,55}; 349 std::copy(boost::begin(v),boost::end(v),boost::begin(sink)); 350 351['asymmetric_coroutine<>::push_type::iterator::operator*()] roughly 352corresponds to __push_coro_op__. An iterator originally obtained from 353__begin__ of an __push_coro__ compares equal to an iterator obtained from 354__end__ of that same __push_coro__ instance when its __push_coro_bool__ would 355return `false`. 356 357 358[heading Exit a __coro_fn__] 359__coro_fn__ is exited with a simple return statement jumping back to the calling 360routine. The __pull_coro__, __push_coro__ becomes complete, e.g. __pull_coro_bool__, 361__push_coro_bool__ will return `false`. 362 363[important After returning from __coro_fn__ the __coro__ is complete (can not 364resumed with __push_coro_op__, __pull_coro_op__).] 365 366 367 368[section:pull_coro Class `asymmetric_coroutine<>::pull_type`] 369 370 #include <boost/coroutine/asymmetric_coroutine.hpp> 371 372 template< typename R > 373 class asymmetric_coroutine<>::pull_type 374 { 375 public: 376 pull_type() noexcept; 377 378 template< typename Fn > 379 pull_type( Fn && fn, attributes const& attr = attributes() ); 380 381 template< typename Fn, typename StackAllocator > 382 pull_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc); 383 384 pull_type( pull_type const& other)=delete; 385 386 pull_type & operator=( pull_type const& other)=delete; 387 388 ~pull_type(); 389 390 pull_type( pull_type && other) noexcept; 391 392 pull_type & operator=( pull_type && other) noexcept; 393 394 operator unspecified-bool-type() const noexcept; 395 396 bool operator!() const noexcept; 397 398 void swap( pull_type & other) noexcept; 399 400 pull_type & operator()(); 401 402 R get() const; 403 }; 404 405 template< typename R > 406 void swap( pull_type< R > & l, pull_type< R > & r); 407 408 template< typename R > 409 range_iterator< pull_type< R > >::type begin( pull_type< R > &); 410 411 template< typename R > 412 range_iterator< pull_type< R > >::type end( pull_type< R > &); 413 414[heading `pull_type()`] 415[variablelist 416[[Effects:] [Creates a coroutine representing __not_a_coro__.]] 417[[Throws:] [Nothing.]] 418] 419 420[heading `template< typename Fn > 421 pull_type( Fn && fn, attributes const& attr)`] 422[variablelist 423[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() 424when ! is_stack_unbounded().]] 425[[Effects:] [Creates a coroutine which will execute `fn`, and enters it. 426Argument `attr` determines stack clean-up.]] 427[[Throws:] [Exceptions thrown inside __coro_fn__.]] 428] 429 430[heading `template< typename Fn, typename StackAllocator > 431 pull_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`] 432[variablelist 433[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() 434when ! is_stack_unbounded().]] 435[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` 436determines stack clean-up. 437For allocating/deallocating the stack `stack_alloc` is used.]] 438[[Throws:] [Exceptions thrown inside __coro_fn__.]] 439] 440 441[heading `~pull_type()`] 442[variablelist 443[[Effects:] [Destroys the context and deallocates the stack.]] 444] 445 446[heading `pull_type( pull_type && other)`] 447[variablelist 448[[Effects:] [Moves the internal data of `other` to `*this`. 449`other` becomes __not_a_coro__.]] 450[[Throws:] [Nothing.]] 451] 452 453[heading `pull_type & operator=( pull_type && other)`] 454[variablelist 455[[Effects:] [Destroys the internal data of `*this` and moves the 456internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] 457[[Throws:] [Nothing.]] 458] 459 460[heading `operator unspecified-bool-type() const`] 461[variablelist 462[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function 463has returned (completed), the function returns `false`. Otherwise `true`.]] 464[[Throws:] [Nothing.]] 465] 466 467[heading `bool operator!() const`] 468[variablelist 469[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function 470has returned (completed), the function returns `true`. Otherwise `false`.]] 471[[Throws:] [Nothing.]] 472] 473 474[heading `pull_type<> & operator()()`] 475[variablelist 476[[Preconditions:] [`*this` is not a __not_a_coro__.]] 477[[Effects:] [Execution control is transferred to __coro_fn__ (no parameter is 478passed to the coroutine-function).]] 479[[Throws:] [Exceptions thrown inside __coro_fn__.]] 480] 481 482[heading `R get()`] 483 484 R asymmetric_coroutine<R,StackAllocator>::pull_type::get(); 485 R& asymmetric_coroutine<R&,StackAllocator>::pull_type::get(); 486 void asymmetric_coroutine<void,StackAllocator>::pull_type::get()=delete; 487 488[variablelist 489[[Preconditions:] [`*this` is not a __not_a_coro__.]] 490[[Returns:] [Returns data transferred from coroutine-function via 491__push_coro_op__.]] 492[[Throws:] [`invalid_result`]] 493[[Note:] [If `R` is a move-only type, you may only call `get()` once before 494the next __pull_coro_op__ call.]] 495] 496 497[heading `void swap( pull_type & other)`] 498[variablelist 499[[Effects:] [Swaps the internal data from `*this` with the values 500of `other`.]] 501[[Throws:] [Nothing.]] 502] 503 504[heading Non-member function `swap()`] 505 506 template< typename R > 507 void swap( pull_type< R > & l, pull_type< R > & r); 508 509[variablelist 510[[Effects:] [As if 'l.swap( r)'.]] 511] 512 513[heading Non-member function `begin( pull_type< R > &)`] 514 template< typename R > 515 range_iterator< pull_type< R > >::type begin( pull_type< R > &); 516 517[variablelist 518[[Returns:] [Returns a range-iterator (input-iterator).]] 519] 520 521[heading Non-member function `end( pull_type< R > &)`] 522 template< typename R > 523 range_iterator< pull_type< R > >::type end( pull_type< R > &); 524 525[variablelist 526[[Returns:] [Returns an end range-iterator (input-iterator).]] 527[[Note:] [When first obtained from `begin( pull_type< R > &)`, or after some 528number of increment operations, an iterator will compare equal to the iterator 529returned by `end( pull_type< R > &)` when the corresponding __pull_coro_bool__ 530would return `false`.]] 531] 532 533[endsect] 534 535 536[section:push_coro Class `asymmetric_coroutine<>::push_type`] 537 538 #include <boost/coroutine/asymmetric_coroutine.hpp> 539 540 template< typename Arg > 541 class asymmetric_coroutine<>::push_type 542 { 543 public: 544 push_type() noexcept; 545 546 template< typename Fn > 547 push_type( Fn && fn, attributes const& attr = attributes() ); 548 549 template< typename Fn, typename StackAllocator > 550 push_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc); 551 552 push_type( push_type const& other)=delete; 553 554 push_type & operator=( push_type const& other)=delete; 555 556 ~push_type(); 557 558 push_type( push_type && other) noexcept; 559 560 push_type & operator=( push_type && other) noexcept; 561 562 operator unspecified-bool-type() const noexcept; 563 564 bool operator!() const noexcept; 565 566 void swap( push_type & other) noexcept; 567 568 push_type & operator()( Arg arg); 569 }; 570 571 template< typename Arg > 572 void swap( push_type< Arg > & l, push_type< Arg > & r); 573 574 template< typename Arg > 575 range_iterator< push_type< Arg > >::type begin( push_type< Arg > &); 576 577 template< typename Arg > 578 range_iterator< push_type< Arg > >::type end( push_type< Arg > &); 579 580[heading `push_type()`] 581[variablelist 582[[Effects:] [Creates a coroutine representing __not_a_coro__.]] 583[[Throws:] [Nothing.]] 584] 585 586[heading `template< typename Fn > 587 push_type( Fn && fn, attributes const& attr)`] 588[variablelist 589[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() 590when ! is_stack_unbounded().]] 591[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` 592determines stack clean-up.]] 593] 594 595[heading `template< typename Fn, typename StackAllocator > 596 push_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`] 597[variablelist 598[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() 599when ! is_stack_unbounded().]] 600[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` 601determines stack clean-up. 602For allocating/deallocating the stack `stack_alloc` is used.]] 603] 604 605[heading `~push_type()`] 606[variablelist 607[[Effects:] [Destroys the context and deallocates the stack.]] 608] 609 610[heading `push_type( push_type && other)`] 611[variablelist 612[[Effects:] [Moves the internal data of `other` to `*this`. 613`other` becomes __not_a_coro__.]] 614[[Throws:] [Nothing.]] 615] 616 617[heading `push_type & operator=( push_type && other)`] 618[variablelist 619[[Effects:] [Destroys the internal data of `*this` and moves the 620internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] 621[[Throws:] [Nothing.]] 622] 623 624[heading `operator unspecified-bool-type() const`] 625[variablelist 626[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function 627has returned (completed), the function returns `false`. Otherwise `true`.]] 628[[Throws:] [Nothing.]] 629] 630 631[heading `bool operator!() const`] 632[variablelist 633[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function 634has returned (completed), the function returns `true`. Otherwise `false`.]] 635[[Throws:] [Nothing.]] 636] 637 638[heading `push_type & operator()(Arg arg)`] 639 640 push_type& asymmetric_coroutine<Arg>::push_type::operator()(Arg); 641 push_type& asymmetric_coroutine<Arg&>::push_type::operator()(Arg&); 642 push_type& asymmetric_coroutine<void>::push_type::operator()(); 643 644[variablelist 645[[Preconditions:] [operator unspecified-bool-type() returns `true` for `*this`.]] 646[[Effects:] [Execution control is transferred to __coro_fn__ and the argument 647`arg` is passed to the coroutine-function.]] 648[[Throws:] [Exceptions thrown inside __coro_fn__.]] 649] 650 651[heading `void swap( push_type & other)`] 652[variablelist 653[[Effects:] [Swaps the internal data from `*this` with the values 654of `other`.]] 655[[Throws:] [Nothing.]] 656] 657 658[heading Non-member function `swap()`] 659 660 template< typename Arg > 661 void swap( push_type< Arg > & l, push_type< Arg > & r); 662 663[variablelist 664[[Effects:] [As if 'l.swap( r)'.]] 665] 666 667[heading Non-member function `begin( push_type< Arg > &)`] 668 template< typename Arg > 669 range_iterator< push_type< Arg > >::type begin( push_type< Arg > &); 670 671[variablelist 672[[Returns:] [Returns a range-iterator (output-iterator).]] 673] 674 675[heading Non-member function `end( push_type< Arg > &)`] 676 template< typename Arg > 677 range_iterator< push_type< Arg > >::type end( push_type< Arg > &); 678 679[variablelist 680[[Returns:] [Returns a end range-iterator (output-iterator).]] 681[[Note:] [When first obtained from `begin( push_type< R > &)`, or after some 682number of increment operations, an iterator will compare equal to the iterator 683returned by `end( push_type< R > &)` when the corresponding __push_coro_bool__ 684would return `false`.]] 685] 686 687[endsect] 688 689 690 691[endsect] 692