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