symmetric.qbk 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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. [section:symmetric Symmetric coroutine]
  8. In contrast to asymmetric coroutines, where the relationship between caller and
  9. callee is fixed, symmetric coroutines are able to transfer execution control
  10. to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required
  11. to return to its direct caller.
  12. [heading __call_coro__]
  13. __call_coro__ starts a symmetric coroutine and transfers its parameter to its
  14. __coro_fn__.
  15. The template parameter defines the transferred parameter type.
  16. The constructor of __call_coro__ takes a function (__coro_fn__) accepting a
  17. reference to a __yield_coro__ as argument. Instantiating a __call_coro__ does
  18. not pass the control of execution to __coro_fn__ - instead the first call of
  19. __call_coro_op__ synthesizes a __yield_coro__ and passes it as reference to
  20. __coro_fn__.
  21. The __call_coro__ interface does not contain a ['get()]-function: you can not
  22. retrieve values from another execution context with this kind of coroutine
  23. object.
  24. [heading __yield_coro__]
  25. __yield_coro_op__ is used to transfer data and execution control to another
  26. context by calling __yield_coro_op__ with another __call_coro__ as first argument.
  27. Alternatively, you may transfer control back to the code that called
  28. __call_coro_op__ by calling __yield_coro_op__ without a __call_coro__ argument.
  29. The class has only one template parameter defining the transferred parameter
  30. type.
  31. Data transferred to the coroutine are accessed through __yield_coro_get__.
  32. [important __yield_coro__ can only be created by the framework.]
  33. std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b)
  34. {
  35. std::vector<int> c;
  36. std::size_t idx_a=0,idx_b=0;
  37. boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0;
  38. boost::coroutines::symmetric_coroutine<void>::call_type coro_a(
  39. [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
  40. while(idx_a<a.size())
  41. {
  42. if(b[idx_b]<a[idx_a]) // test if element in array b is less than in array a
  43. yield(*other_b); // yield to coroutine coro_b
  44. c.push_back(a[idx_a++]); // add element to final array
  45. }
  46. // add remaining elements of array b
  47. while ( idx_b < b.size())
  48. c.push_back( b[idx_b++]);
  49. });
  50. boost::coroutines::symmetric_coroutine<void>::call_type coro_b(
  51. [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
  52. while(idx_b<b.size())
  53. {
  54. if (a[idx_a]<b[idx_b]) // test if element in array a is less than in array b
  55. yield(*other_a); // yield to coroutine coro_a
  56. c.push_back(b[idx_b++]); // add element to final array
  57. }
  58. // add remaining elements of array a
  59. while ( idx_a < a.size())
  60. c.push_back( a[idx_a++]);
  61. });
  62. other_a = & coro_a;
  63. other_b = & coro_b;
  64. coro_a(); // enter coroutine-fn of coro_a
  65. return c;
  66. }
  67. std::vector< int > a = {1,5,6,10};
  68. std::vector< int > b = {2,4,7,8,9,13};
  69. std::vector< int > c = merge(a,b);
  70. print(a);
  71. print(b);
  72. print(c);
  73. output:
  74. a : 1 5 6 10
  75. b : 2 4 7 8 9 13
  76. c : 1 2 4 5 6 7 8 9 10 13
  77. In this example two __call_coro__ are created in the main execution context
  78. accepting a lambda function (== __coro_fn__) which merges elements of two
  79. sorted arrays into a third array.
  80. `coro_a()` enters the __coro_fn__ of `coro_a` cycling through the array and
  81. testing if the actual element in the other array is less than the element in
  82. the local one. If so, the coroutine yields to the other coroutine `coro_b`
  83. using `yield(*other_b)`. If the current element of the local array is less
  84. than the element of the other array, it is put to the third array.
  85. Because the coroutine jumps back to `coro_a()` (returning from this method)
  86. after leaving the __coro_fn__, the elements of the other array will appended
  87. at the end of the third array if all element of the local array are processed.
  88. [heading coroutine-function]
  89. The __coro_fn__ returns ['void] and takes __yield_coro__, providing
  90. coroutine functionality inside the __coro_fn__, as argument. Using this
  91. instance is the only way to transfer data and execution control.
  92. __call_coro__ does not enter the __coro_fn__ at __call_coro__ construction but
  93. at the first invocation of __call_coro_op__.
  94. Unless the template parameter is `void`, the __coro_fn__ of a
  95. __call_coro__ can assume that (a) upon initial entry and (b) after every
  96. __yield_coro_op__ call, its __yield_coro_get__ has a new value available.
  97. However, if the template parameter is a move-only type,
  98. __yield_coro_get__ may only be called once before the next __yield_coro_op__
  99. call.
  100. [heading passing data from main-context to a symmetric-coroutine]
  101. In order to transfer data to a __call_coro__ from the main-context the
  102. framework synthesizes a __yield_coro__ associated with the __call_coro__
  103. instance. The synthesized __yield_coro__ is passed as argument to __coro_fn__.
  104. The main-context must call __call_coro_op__ in order to transfer each data value
  105. into the __coro_fn__.
  106. Access to the transferred data value is given by __yield_coro_get__.
  107. boost::coroutines::symmetric_coroutine<int>::call_type coro( // constructor does NOT enter coroutine-function
  108. [&](boost::coroutines::symmetric_coroutine<int>::yield_type& yield){
  109. for (;;) {
  110. std::cout << yield.get() << " ";
  111. yield(); // jump back to starting context
  112. }
  113. });
  114. coro(1); // transfer {1} to coroutine-function
  115. coro(2); // transfer {2} to coroutine-function
  116. coro(3); // transfer {3} to coroutine-function
  117. coro(4); // transfer {4} to coroutine-function
  118. coro(5); // transfer {5} to coroutine-function
  119. [heading exceptions]
  120. An uncaught exception inside a __call_coro__'s __coro_fn__ will call
  121. __terminate__.
  122. [important Code executed by coroutine must not prevent the propagation of the
  123. __forced_unwind__ exception. Absorbing that exception will cause stack
  124. unwinding to fail. Thus, any code that catches all exceptions must re-throw any
  125. pending __forced_unwind__ exception.]
  126. try {
  127. // code that might throw
  128. } catch(const boost::coroutines::detail::forced_unwind&) {
  129. throw;
  130. } catch(...) {
  131. // possibly not re-throw pending exception
  132. }
  133. [important Do not jump from inside a catch block and then re-throw the
  134. exception in another execution context.]
  135. [heading Stack unwinding]
  136. Sometimes it is necessary to unwind the stack of an unfinished coroutine to
  137. destroy local stack variables so they can release allocated resources (RAII
  138. pattern). The `attributes` argument of the coroutine constructor indicates
  139. whether the destructor should unwind the stack (stack is unwound by default).
  140. Stack unwinding assumes the following preconditions:
  141. * The coroutine is not __not_a_coro__
  142. * The coroutine is not complete
  143. * The coroutine is not running
  144. * The coroutine owns a stack
  145. After unwinding, a __coro__ is complete.
  146. struct X {
  147. X(){
  148. std::cout<<"X()"<<std::endl;
  149. }
  150. ~X(){
  151. std::cout<<"~X()"<<std::endl;
  152. }
  153. };
  154. boost::coroutines::symmetric_coroutine<int>::call_type other_coro(...);
  155. {
  156. boost::coroutines::symmetric_coroutine<void>::call_type coro(
  157. [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){
  158. X x;
  159. std::cout<<"fn()"<<std::endl;
  160. // transfer execution control to other coroutine
  161. yield( other_coro, 7);
  162. });
  163. coro();
  164. std::cout<<"coro is complete: "<<std::boolalpha<<!coro<<"\n";
  165. }
  166. output:
  167. X()
  168. fn()
  169. coro is complete: false
  170. ~X()
  171. [heading Exit a __coro_fn__]
  172. __coro_fn__ is exited with a simple return statement. This jumps back to the
  173. calling __call_coro_op__ at the start of symmetric coroutine chain. That is,
  174. symmetric coroutines do not have a strong, fixed relationship to the caller as
  175. do asymmetric coroutines. The __call_coro__ becomes complete, e.g.
  176. __call_coro_bool__ will return `false`.
  177. [important After returning from __coro_fn__ the __coro__ is complete (can not be
  178. resumed with __call_coro_op__).]
  179. [section:symmetric_coro Class `symmetric_coroutine<>::call_type`]
  180. #include <boost/coroutine/symmetric_coroutine.hpp>
  181. template< typename Arg >
  182. class symmetric_coroutine<>::call_type
  183. {
  184. public:
  185. call_type() noexcept;
  186. template< typename Fn >
  187. call_type( Fn && fn, attributes const& attr = attributes() );
  188. template< typename Fn, typename StackAllocator >
  189. call_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc);
  190. ~call_type();
  191. call_type( call_type const& other)=delete;
  192. call_type & operator=( call_type const& other)=delete;
  193. call_type( call_type && other) noexcept;
  194. call_type & operator=( call_type && other) noexcept;
  195. operator unspecified-bool-type() const;
  196. bool operator!() const noexcept;
  197. void swap( call_type & other) noexcept;
  198. call_type & operator()( Arg arg) noexcept;
  199. };
  200. template< typename Arg >
  201. void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
  202. [heading `call_type()`]
  203. [variablelist
  204. [[Effects:] [Creates a coroutine representing __not_a_coro__.]]
  205. [[Throws:] [Nothing.]]
  206. ]
  207. [heading `template< typename Fn >
  208. call_type( Fn fn, attributes const& attr)`]
  209. [variablelist
  210. [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
  211. when ! is_stack_unbounded().]]
  212. [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
  213. determines stack clean-up.
  214. For allocating/deallocating the stack `stack_alloc` is used.]]
  215. ]
  216. [heading `template< typename Fn, typename StackAllocator >
  217. call_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`]
  218. [variablelist
  219. [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
  220. when ! is_stack_unbounded().]]
  221. [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
  222. determines stack clean-up.
  223. For allocating/deallocating the stack `stack_alloc` is used.]]
  224. ]
  225. [heading `~call_type()`]
  226. [variablelist
  227. [[Effects:] [Destroys the context and deallocates the stack.]]
  228. ]
  229. [heading `call_type( call_type && other)`]
  230. [variablelist
  231. [[Effects:] [Moves the internal data of `other` to `*this`.
  232. `other` becomes __not_a_coro__.]]
  233. [[Throws:] [Nothing.]]
  234. ]
  235. [heading `call_type & operator=( call_type && other)`]
  236. [variablelist
  237. [[Effects:] [Destroys the internal data of `*this` and moves the
  238. internal data of `other` to `*this`. `other` becomes __not_a_coro__.]]
  239. [[Throws:] [Nothing.]]
  240. ]
  241. [heading `operator unspecified-bool-type() const`]
  242. [variablelist
  243. [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
  244. has returned (completed), the function returns `false`. Otherwise `true`.]]
  245. [[Throws:] [Nothing.]]
  246. ]
  247. [heading `bool operator!() const`]
  248. [variablelist
  249. [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
  250. has returned (completed), the function returns `true`. Otherwise `false`.]]
  251. [[Throws:] [Nothing.]]
  252. ]
  253. [heading `void swap( call_type & other)`]
  254. [variablelist
  255. [[Effects:] [Swaps the internal data from `*this` with the values
  256. of `other`.]]
  257. [[Throws:] [Nothing.]]
  258. ]
  259. [heading `call_type & operator()(Arg arg)`]
  260. symmetric_coroutine::call_type& coroutine<Arg,StackAllocator>::call_type::operator()(Arg);
  261. symmetric_coroutine::call_type& coroutine<Arg&,StackAllocator>::call_type::operator()(Arg&);
  262. symmetric_coroutine::call_type& coroutine<void,StackAllocator>::call_type::operator()();
  263. [variablelist
  264. [[Preconditions:] [operator unspecified-bool-type() returns `true` for `*this`.]]
  265. [[Effects:] [Execution control is transferred to __coro_fn__ and the argument
  266. `arg` is passed to the coroutine-function.]]
  267. [[Throws:] [Nothing.]]
  268. ]
  269. [heading Non-member function `swap()`]
  270. template< typename Arg >
  271. void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
  272. [variablelist
  273. [[Effects:] [As if 'l.swap( r)'.]]
  274. ]
  275. [endsect]
  276. [section:yield_coro Class `symmetric_coroutine<>::yield_type`]
  277. #include <boost/coroutine/symmetric_coroutine.hpp>
  278. template< typename R >
  279. class symmetric_coroutine<>::yield_type
  280. {
  281. public:
  282. yield_type() noexcept;
  283. yield_type( yield_type const& other)=delete;
  284. yield_type & operator=( yield_type const& other)=delete;
  285. yield_type( yield_type && other) noexcept;
  286. yield_type & operator=( yield_type && other) noexcept;
  287. void swap( yield_type & other) noexcept;
  288. operator unspecified-bool-type() const;
  289. bool operator!() const noexcept;
  290. yield_type & operator()();
  291. template< typename X >
  292. yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
  293. template< typename X >
  294. yield_type & operator()( symmetric_coroutine< X >::call_type & other);
  295. R get() const;
  296. };
  297. [heading `operator unspecified-bool-type() const`]
  298. [variablelist
  299. [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `false`.
  300. Otherwise `true`.]]
  301. [[Throws:] [Nothing.]]
  302. ]
  303. [heading `bool operator!() const`]
  304. [variablelist
  305. [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `true`.
  306. Otherwise `false`.]]
  307. [[Throws:] [Nothing.]]
  308. ]
  309. [heading `yield_type & operator()()`]
  310. yield_type & operator()();
  311. template< typename X >
  312. yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
  313. template<>
  314. yield_type & operator()( symmetric_coroutine< void >::call_type & other);
  315. [variablelist
  316. [[Preconditions:] [`*this` is not a __not_a_coro__.]]
  317. [[Effects:] [The first function transfers execution control back to the starting point,
  318. e.g. invocation of __call_coro_op__. The last two functions transfer the execution control
  319. to another symmetric coroutine. Parameter `x` is passed as value into `other`'s context.]]
  320. [[Throws:] [__forced_unwind__]]
  321. ]
  322. [heading `R get()`]
  323. R symmetric_coroutine<R>::yield_type::get();
  324. R& symmetric_coroutine<R&>::yield_type::get();
  325. void symmetric_coroutine<void>yield_type::get()=delete;
  326. [variablelist
  327. [[Preconditions:] [`*this` is not a __not_a_coro__.]]
  328. [[Returns:] [Returns data transferred from coroutine-function via
  329. __call_coro_op__.]]
  330. [[Throws:] [`invalid_result`]]
  331. ]
  332. [endsect]
  333. [endsect]