asymmetric.qbk 22 KB

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