| [/ |
| Copyright Oliver Kowalke 2009. |
| Distributed under the Boost Software License, Version 1.0. |
| (See accompanying file LICENSE_1_0.txt or copy at |
| http://www.boost.org/LICENSE_1_0.txt |
| ] |
| |
| [section:symmetric Symmetric coroutine] |
| |
| In contrast to asymmetric coroutines, where the relationship between caller and |
| callee is fixed, symmetric coroutines are able to transfer execution control |
| to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required |
| to return to its direct caller. |
| |
| |
| [heading __call_coro__] |
| __call_coro__ starts a symmetric coroutine and transfers its parameter to its |
| __coro_fn__. |
| The template parameter defines the transferred parameter type. |
| The constructor of __call_coro__ takes a function (__coro_fn__) accepting a |
| reference to a __yield_coro__ as argument. Instantiating a __call_coro__ does |
| not pass the control of execution to __coro_fn__ - instead the first call of |
| __call_coro_op__ synthesizes a __yield_coro__ and passes it as reference to |
| __coro_fn__. |
| |
| The __call_coro__ interface does not contain a ['get()]-function: you can not |
| retrieve values from another execution context with this kind of coroutine |
| object. |
| |
| |
| [heading __yield_coro__] |
| __yield_coro_op__ is used to transfer data and execution control to another |
| context by calling __yield_coro_op__ with another __call_coro__ as first argument. |
| Alternatively, you may transfer control back to the code that called |
| __call_coro_op__ by calling __yield_coro_op__ without a __call_coro__ argument. |
| |
| The class has only one template parameter defining the transferred parameter |
| type. |
| Data transferred to the coroutine are accessed through __yield_coro_get__. |
| |
| [important __yield_coro__ can only be created by the framework.] |
| |
| std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b) |
| { |
| std::vector<int> c; |
| std::size_t idx_a=0,idx_b=0; |
| boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0; |
| |
| boost::coroutines::symmetric_coroutine<void>::call_type coro_a( |
| [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) { |
| while(idx_a<a.size()) |
| { |
| if(b[idx_b]<a[idx_a]) // test if element in array b is less than in array a |
| yield(*other_b); // yield to coroutine coro_b |
| c.push_back(a[idx_a++]); // add element to final array |
| } |
| // add remaining elements of array b |
| while ( idx_b < b.size()) |
| c.push_back( b[idx_b++]); |
| }); |
| |
| boost::coroutines::symmetric_coroutine<void>::call_type coro_b( |
| [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) { |
| while(idx_b<b.size()) |
| { |
| if (a[idx_a]<b[idx_b]) // test if element in array a is less than in array b |
| yield(*other_a); // yield to coroutine coro_a |
| c.push_back(b[idx_b++]); // add element to final array |
| } |
| // add remaining elements of array a |
| while ( idx_a < a.size()) |
| c.push_back( a[idx_a++]); |
| }); |
| |
| |
| other_a = & coro_a; |
| other_b = & coro_b; |
| |
| coro_a(); // enter coroutine-fn of coro_a |
| |
| return c; |
| } |
| |
| std::vector< int > a = {1,5,6,10}; |
| std::vector< int > b = {2,4,7,8,9,13}; |
| std::vector< int > c = merge(a,b); |
| print(a); |
| print(b); |
| print(c); |
| |
| output: |
| a : 1 5 6 10 |
| b : 2 4 7 8 9 13 |
| c : 1 2 4 5 6 7 8 9 10 13 |
| |
| In this example two __call_coro__ are created in the main execution context |
| accepting a lambda function (== __coro_fn__) which merges elements of two |
| sorted arrays into a third array. |
| `coro_a()` enters the __coro_fn__ of `coro_a` cycling through the array and |
| testing if the actual element in the other array is less than the element in |
| the local one. If so, the coroutine yields to the other coroutine `coro_b` |
| using `yield(*other_b)`. If the current element of the local array is less |
| than the element of the other array, it is put to the third array. |
| Because the coroutine jumps back to `coro_a()` (returning from this method) |
| after leaving the __coro_fn__, the elements of the other array will appended |
| at the end of the third array if all element of the local array are processed. |
| |
| |
| [heading coroutine-function] |
| The __coro_fn__ returns ['void] and takes __yield_coro__, providing |
| coroutine functionality inside the __coro_fn__, as argument. Using this |
| instance is the only way to transfer data and execution control. |
| __call_coro__ does not enter the __coro_fn__ at __call_coro__ construction but |
| at the first invocation of __call_coro_op__. |
| |
| Unless the template parameter is `void`, the __coro_fn__ of a |
| __call_coro__ can assume that (a) upon initial entry and (b) after every |
| __yield_coro_op__ call, its __yield_coro_get__ has a new value available. |
| |
| However, if the template parameter is a move-only type, |
| __yield_coro_get__ may only be called once before the next __yield_coro_op__ |
| call. |
| |
| [heading passing data from main-context to a symmetric-coroutine] |
| In order to transfer data to a __call_coro__ from the main-context the |
| framework synthesizes a __yield_coro__ associated with the __call_coro__ |
| instance. The synthesized __yield_coro__ is passed as argument to __coro_fn__. |
| The main-context must call __call_coro_op__ in order to transfer each data value |
| into the __coro_fn__. |
| Access to the transferred data value is given by __yield_coro_get__. |
| |
| boost::coroutines::symmetric_coroutine<int>::call_type coro( // constructor does NOT enter coroutine-function |
| [&](boost::coroutines::symmetric_coroutine<int>::yield_type& yield){ |
| for (;;) { |
| std::cout << yield.get() << " "; |
| yield(); // jump back to starting context |
| } |
| }); |
| |
| coro(1); // transfer {1} to coroutine-function |
| coro(2); // transfer {2} to coroutine-function |
| coro(3); // transfer {3} to coroutine-function |
| coro(4); // transfer {4} to coroutine-function |
| coro(5); // transfer {5} to coroutine-function |
| |
| |
| [heading exceptions] |
| An uncaught exception inside a __call_coro__'s __coro_fn__ will call |
| __terminate__. |
| |
| [important Code executed by coroutine must not prevent the propagation of the |
| __forced_unwind__ exception. Absorbing that exception will cause stack |
| unwinding to fail. Thus, any code that catches all exceptions must re-throw any |
| pending __forced_unwind__ exception.] |
| |
| try { |
| // code that might throw |
| } catch(const boost::coroutines::detail::forced_unwind&) { |
| throw; |
| } catch(...) { |
| // possibly not re-throw pending exception |
| } |
| |
| [important Do not jump from inside a catch block and than re-throw the |
| exception in another execution context.] |
| |
| |
| [heading Stack unwinding] |
| Sometimes it is necessary to unwind the stack of an unfinished coroutine to |
| destroy local stack variables so they can release allocated resources (RAII |
| pattern). The `attributes` argument of the coroutine constructor indicates |
| whether the destructor should unwind the stack (stack is unwound by default). |
| |
| Stack unwinding assumes the following preconditions: |
| |
| * The coroutine is not __not_a_coro__ |
| * The coroutine is not complete |
| * The coroutine is not running |
| * The coroutine owns a stack |
| |
| After unwinding, a __coro__ is complete. |
| |
| struct X { |
| X(){ |
| std::cout<<"X()"<<std::endl; |
| } |
| |
| ~X(){ |
| std::cout<<"~X()"<<std::endl; |
| } |
| }; |
| |
| boost::coroutines::symmetric_coroutine<int>::call_type other_coro(...); |
| |
| { |
| boost::coroutines::symmetric_coroutine<void>::call_type coro( |
| [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){ |
| X x; |
| std::cout<<"fn()"<<std::endl; |
| // transfer execution control to other coroutine |
| yield( other_coro, 7); |
| }); |
| |
| coro(); |
| |
| std::cout<<"coro is complete: "<<std::boolalpha<<!coro<<"\n"; |
| } |
| |
| output: |
| X() |
| fn() |
| coro is complete: false |
| ~X() |
| |
| |
| [heading Exit a __coro_fn__] |
| __coro_fn__ is exited with a simple return statement. This jumps back to the |
| calling __call_coro_op__ at the start of symmetric coroutine chain. That is, |
| symmetric coroutines do not have a strong, fixed relationship to the caller as |
| do asymmetric coroutines. The __call_coro__ becomes complete, e.g. |
| __call_coro_bool__ will return `false`. |
| |
| [important After returning from __coro_fn__ the __coro__ is complete (can not be |
| resumed with __call_coro_op__).] |
| |
| |
| [section:symmetric_coro Class `symmetric_coroutine<>::call_type`] |
| |
| #include <boost/coroutine/symmetric_coroutine.hpp> |
| |
| template< typename Arg > |
| class symmetric_coroutine<>::call_type |
| { |
| public: |
| call_type() noexcept; |
| |
| template< typename Fn > |
| call_type( Fn && fn, attributes const& attr = attributes() ); |
| |
| template< typename Fn, typename StackAllocator > |
| call_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc); |
| |
| ~call_type(); |
| |
| call_type( call_type const& other)=delete; |
| |
| call_type & operator=( call_type const& other)=delete; |
| |
| call_type( call_type && other) noexcept; |
| |
| call_type & operator=( call_type && other) noexcept; |
| |
| operator unspecified-bool-type() const; |
| |
| bool operator!() const noexcept; |
| |
| void swap( call_type & other) noexcept; |
| |
| call_type & operator()( Arg arg) noexcept; |
| }; |
| |
| template< typename Arg > |
| void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r); |
| |
| [heading `call_type()`] |
| [variablelist |
| [[Effects:] [Creates a coroutine representing __not_a_coro__.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `template< typename Fn > |
| call_type( Fn fn, attributes const& attr)`] |
| [variablelist |
| [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() |
| when ! is_stack_unbounded().]] |
| [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` |
| determines stack clean-up and preserving floating-point registers. |
| For allocating/deallocating the stack `stack_alloc` is used.]] |
| ] |
| |
| [heading `template< typename Fn, typename StackAllocator > |
| call_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`] |
| [variablelist |
| [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() |
| when ! is_stack_unbounded().]] |
| [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` |
| determines stack clean-up and preserving floating-point registers. |
| For allocating/deallocating the stack `stack_alloc` is used.]] |
| ] |
| |
| [heading `~call_type()`] |
| [variablelist |
| [[Effects:] [Destroys the context and deallocates the stack.]] |
| ] |
| |
| [heading `call_type( call_type && other)`] |
| [variablelist |
| [[Effects:] [Moves the internal data of `other` to `*this`. |
| `other` becomes __not_a_coro__.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `call_type & operator=( call_type && other)`] |
| [variablelist |
| [[Effects:] [Destroys the internal data of `*this` and moves the |
| internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `operator unspecified-bool-type() const`] |
| [variablelist |
| [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function |
| has returned (completed), the function returns `false`. Otherwise `true`.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `bool operator!() const`] |
| [variablelist |
| [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function |
| has returned (completed), the function returns `true`. Otherwise `false`.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `void swap( call_type & other)`] |
| [variablelist |
| [[Effects:] [Swaps the internal data from `*this` with the values |
| of `other`.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `call_type & operator()(Arg arg)`] |
| |
| symmetric_coroutine::call_type& coroutine<Arg,StackAllocator>::call_type::operator()(Arg); |
| symmetric_coroutine::call_type& coroutine<Arg&,StackAllocator>::call_type::operator()(Arg&); |
| symmetric_coroutine::call_type& coroutine<void,StackAllocator>::call_type::operator()(); |
| |
| [variablelist |
| [[Preconditions:] [operator unspecified-bool-type() returns `true` for `*this`.]] |
| [[Effects:] [Execution control is transferred to __coro_fn__ and the argument |
| `arg` is passed to the coroutine-function.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading Non-member function `swap()`] |
| |
| template< typename Arg > |
| void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r); |
| |
| [variablelist |
| [[Effects:] [As if 'l.swap( r)'.]] |
| ] |
| |
| [endsect] |
| |
| |
| |
| [section:yield_coro Class `symmetric_coroutine<>::yield_type`] |
| |
| #include <boost/coroutine/symmetric_coroutine.hpp> |
| |
| template< typename R > |
| class symmetric_coroutine<>::yield_type |
| { |
| public: |
| yield_type() noexcept; |
| |
| yield_type( yield_type const& other)=delete; |
| |
| yield_type & operator=( yield_type const& other)=delete; |
| |
| yield_type( yield_type && other) noexcept; |
| |
| yield_type & operator=( yield_type && other) noexcept; |
| |
| void swap( yield_type & other) noexcept; |
| |
| operator unspecified-bool-type() const; |
| |
| bool operator!() const noexcept; |
| |
| yield_type & operator()(); |
| |
| template< typename X > |
| yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x); |
| |
| template< typename X > |
| yield_type & operator()( symmetric_coroutine< X >::call_type & other); |
| |
| R get() const; |
| }; |
| |
| [heading `operator unspecified-bool-type() const`] |
| [variablelist |
| [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `false`. |
| Otherwise `true`.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `bool operator!() const`] |
| [variablelist |
| [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `true`. |
| Otherwise `false`.]] |
| [[Throws:] [Nothing.]] |
| ] |
| |
| [heading `yield_type & operator()()`] |
| |
| yield_type & operator()(); |
| template< typename X > |
| yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x); |
| template<> |
| yield_type & operator()( symmetric_coroutine< void >::call_type & other); |
| |
| [variablelist |
| [[Preconditions:] [`*this` is not a __not_a_coro__.]] |
| [[Effects:] [The first function transfers execution control back to the starting point, |
| e.g. invocation of __call_coro_op__. The last two functions transfer the execution control |
| to another symmetric coroutine. Parameter `x` is passed as value into `other`'s context.]] |
| [[Throws:] [__forced_unwind__]] |
| ] |
| |
| [heading `R get()`] |
| |
| R symmetric_coroutine<R>::yield_type::get(); |
| R& symmetric_coroutine<R&>::yield_type::get(); |
| void symmetric_coroutine<void>yield_type::get()=delete; |
| |
| [variablelist |
| [[Preconditions:] [`*this` is not a __not_a_coro__.]] |
| [[Returns:] [Returns data transferred from coroutine-function via |
| __push_coro_op__.]] |
| [[Throws:] [`invalid_result`]] |
| ] |
| |
| [endsect] |
| |
| |
| |
| [endsect] |