[/ Copyright Oliver Kowalke 2014. 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:asymmetric Asymmetric coroutine] Two asymmetric coroutine types - __push_coro__ and __pull_coro__ - provide a unidirectional transfer of data. [note ['asymmetric_coroutine<>] is a typedef of __coro__.] [heading __pull_coro__] __pull_coro__ transfers data from another execution context (== pulled-from). The template parameter defines the transferred parameter type. The constructor of __pull_coro__ takes a function (__coro_fn__) accepting a reference to an __push_coro__ as argument. Instantiating an __pull_coro__ passes the control of execution to __coro_fn__ and a complementary __push_coro__ is synthesized by the library and passed as reference to __coro_fn__. This kind of coroutine provides __pull_coro_op__. This method only switches context; it transfers no data. __pull_coro__ provides input iterators (__pull_coro_it__) and __begin__/__end__ are overloaded. The increment-operation switches the context and transfers data. typedef boost::coroutines2::coroutine coro_t; coro_t::pull_type source( [&](coro_t::push_type& sink){ int first=1,second=1; sink(first); sink(second); for(int i=0;i<8;++i){ int third=first+second; first=second; second=third; sink(third); } }); for(auto i:source) std::cout << i << " "; output: 1 1 2 3 5 8 13 21 34 55 In this example an __pull_coro__ is created in the main execution context taking a lambda function (== __coro_fn__) which calculates Fibonacci numbers in a simple ['for]-loop. The __coro_fn__ is executed in a newly created execution context which is managed by the instance of __pull_coro__. An __push_coro__ is automatically generated by the library and passed as reference to the lambda function. Each time the lambda function calls __push_coro_op__ with another Fibonacci number, __push_coro__ transfers it back to the main execution context. The local state of __coro_fn__ is preserved and will be restored upon transferring execution control back to __coro_fn__ to calculate the next Fibonacci number. Because __pull_coro__ provides input iterators and __begin__/__end__ are overloaded, a ['range-based for]-loop can be used to iterate over the generated Fibonacci numbers. [heading __push_coro__] __push_coro__ transfers data to the other execution context (== pushed-to). The template parameter defines the transferred parameter type. The constructor of __push_coro__ takes a function (__coro_fn__) accepting a reference to an __pull_coro__ as argument. In contrast to __pull_coro__, instantiating an __push_coro__ does not pass the control of execution to __coro_fn__ - instead the first call of __push_coro_op__ synthesizes a complementary __pull_coro__ and passes it as reference to __coro_fn__. The __push_coro__ interface does not contain a ['get()]-function: you can not retrieve values from another execution context with this kind of coroutine. __push_coro__ provides output iterators (__push_coro_it__) and __begin__/__end__ are overloaded. The increment-operation switches the context and transfers data. typedef boost::coroutines2::coroutine coro_t; struct FinalEOL{ ~FinalEOL(){ std::cout << std::endl; } }; const int num=5, width=15; coro_t::push_type writer( [&](coro_t::pull_type& in){ // finish the last line when we leave by whatever means FinalEOL eol; // pull values from upstream, lay them out 'num' to a line for (;;){ for(int i=0;i words{ "peas", "porridge", "hot", "peas", "porridge", "cold", "peas", "porridge", "in", "the", "pot", "nine", "days", "old" }; std::copy(begin(words),end(words),begin(writer)); output: peas porridge hot peas porridge cold peas porridge in the pot nine days old In this example an __push_coro__ is created in the main execution context accepting a lambda function (== __coro_fn__) which requests strings and lays out 'num' of them on each line. This demonstrates the inversion of control permitted by coroutines. Without coroutines, a utility function to perform the same job would necessarily accept each new value as a function parameter, returning after processing that single value. That function would depend on a static state variable. A __coro_fn__, however, can request each new value as if by calling a function -- even though its caller also passes values as if by calling a function. The __coro_fn__ is executed in a newly created execution context which is managed by the instance of __push_coro__. The main execution context passes the strings to the __coro_fn__ by calling __push_coro_op__. An __pull_coro__ instance is automatically generated by the library and passed as reference to the lambda function. The __coro_fn__ accesses the strings passed from the main execution context by calling __pull_coro_get__ and lays those strings out on ['std::cout] according the parameters 'num' and 'width'. The local state of __coro_fn__ is preserved and will be restored after transferring execution control back to __coro_fn__. Because __push_coro__ provides output iterators and __begin__/__end__ are overloaded, the ['std::copy] algorithm can be used to iterate over the vector containing the strings and pass them one by one to the coroutine. [heading coroutine-function] The __coro_fn__ returns ['void] and takes its counterpart-coroutine as argument, so that using the coroutine passed as argument to __coro_fn__ is the only way to transfer data and execution control back to the caller. Both coroutine types take the same template argument. For __pull_coro__ the __coro_fn__ is entered at __pull_coro__ construction. For __push_coro__ the __coro_fn__ is not entered at __push_coro__ construction but entered by the first invocation of __push_coro_op__. After execution control is returned from __coro_fn__ the state of the coroutine can be checked via __pull_coro_bool__ returning `true` if the coroutine is still valid (__coro_fn__ has not terminated). Unless the first template parameter is `void`, `true` also implies that a data value is available. [heading passing data from a pull-coroutine to main-context] In order to transfer data from an __pull_coro__ to the main-context the framework synthesizes an __push_coro__ associated with the __pull_coro__ instance in the main-context. The synthesized __push_coro__ is passed as argument to __coro_fn__. The __coro_fn__ must call this __push_coro_op__ in order to transfer each data value back to the main-context. In the main-context, the __pull_coro_bool__ determines whether the coroutine is still valid and a data value is available or __coro_fn__ has terminated (__pull_coro__ is invalid; no data value available). Access to the transferred data value is given by __pull_coro_get__. typedef boost::coroutines2::coroutine coro_t; coro_t::pull_type source( // constructor enters coroutine-function [&](coro_t::push_type& sink){ sink(1); // push {1} back to main-context sink(1); // push {1} back to main-context sink(2); // push {2} back to main-context sink(3); // push {3} back to main-context sink(5); // push {5} back to main-context sink(8); // push {8} back to main-context }); while(source){ // test if pull-coroutine is valid int ret=source.get(); // access data value source(); // context-switch to coroutine-function } [heading passing data from main-context to a push-coroutine] In order to transfer data to an __push_coro__ from the main-context the framework synthesizes an __pull_coro__ associated with the __push_coro__ instance in the main-context. The synthesized __pull_coro__ is passed as argument to __coro_fn__. The main-context must call this __push_coro_op__ in order to transfer each data value into the __coro_fn__. Access to the transferred data value is given by __pull_coro_get__. typedef boost::coroutines2::coroutine coro_t; coro_t::push_type sink( // constructor does NOT enter coroutine-function [&](coro_t::pull_type& source){ for (int i:source) { std::cout << i << " "; } }); std::vector v{1,1,2,3,5,8,13,21,34,55}; for( int i:v){ sink(i); // push {i} to coroutine-function } [heading accessing parameters] Parameters returned from or transferred to the __coro_fn__ can be accessed with __pull_coro_get__. Splitting-up the access of parameters from context switch function enables to check if __pull_coro__ is valid after return from __pull_coro_op__, e.g. __pull_coro__ has values and __coro_fn__ has not terminated. typedef boost::coroutines2::coroutine> coro_t; coro_t::push_type sink( [&](coro_t::pull_type& source){ // access tuple {7,11}; x==7 y==1 int x,y; boost::tie(x,y)=source.get(); }); sink(boost::make_tuple(7,11)); [heading exceptions] An exception thrown inside an __pull_coro__'s __coro_fn__ before its first call to __push_coro_op__ will be re-thrown by the __pull_coro__ constructor. After an __pull_coro__'s __coro_fn__'s first call to __push_coro_op__, any subsequent exception inside that __coro_fn__ will be re-thrown by __pull_coro_op__. __pull_coro_get__ does not throw. An exception thrown inside an __push_coro__'s __coro_fn__ will be re-thrown by __push_coro_op__. [important Code executed by __coro_fn__ 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::coroutines2::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()"<::push_type coro_t; coro_t::push_type sink( [&](coro_t::pull_type& source){ X x; for(int=0;;++i){ std::cout<<"fn(): "< coro_t; int number=2,exponent=8; coro_t::pull_type source( [&](coro_t::push_type & sink){ int counter=0,result=1; while(counter++::pull_type::iterator::operator++()] corresponds to __pull_coro_op__; ['coroutine<>::pull_type::iterator::operator*()] roughly corresponds to __pull_coro_get__. An iterator originally obtained from __begin__ of an __pull_coro__ compares equal to an iterator obtained from __end__ of that same __pull_coro__ instance when its __pull_coro_bool__ would return `false`]. [note If `T` is a move-only type, then ['coroutine::pull_type::iterator] may only be dereferenced once before it is incremented again.] Output-iterators can be created from __push_coro__. typedef boost::coroutines2::coroutine coro_t; coro_t::push_type sink( [&](coro_t::pull_type& source){ while(source){ std::cout << source.get() << " "; source(); } }); std::vector v{1,1,2,3,5,8,13,21,34,55}; std::copy(begin(v),end(v),begin(sink)); ['coroutine<>::push_type::iterator::operator*()] roughly corresponds to __push_coro_op__. An iterator originally obtained from __begin__ of an __push_coro__ compares equal to an iterator obtained from __end__ of that same __push_coro__ instance when its __push_coro_bool__ would return `false`. [heading Exit a __coro_fn__] __coro_fn__ is exited with a simple return statement jumping back to the calling routine. The __pull_coro__, __push_coro__ becomes complete, e.g. __pull_coro_bool__, __push_coro_bool__ will return `false`. [important After returning from __coro_fn__ the __coro__ is complete (can not resumed with __push_coro_op__, __pull_coro_op__).] [section:pull_coro Class `coroutine<>::pull_type`] #include template< typename R > class coroutine<>::pull_type { public: template< typename Fn > pull_type( Fn && fn); template< typename StackAllocator, typename Fn > pull_type( StackAllocator stack_alloc, Fn && fn); pull_type( pull_type const& other)=delete; pull_type & operator=( pull_type const& other)=delete; ~pull_type(); pull_type( pull_type && other) noexcept; pull_type & operator=( pull_type && other) noexcept; pull_coroutine & operator()(); explicit operator bool() const noexcept; bool operator!() const noexcept; R get() noexcept; }; template< typename R > range_iterator< pull_type< R > >::type begin( pull_type< R > &); template< typename R > range_iterator< pull_type< R > >::type end( pull_type< R > &); [heading `template< typename Fn > pull_type( Fn && fn)`] [variablelist [[Effects:] [Creates a coroutine which will execute `fn`, and enters it.]] [[Throws:] [Exceptions thrown inside __coro_fn__.]] ] [heading `template< typename StackAllocator, typename Fn > pull_type( StackAllocator const& stack_alloc, Fn && fn)`] [variablelist [[Effects:] [Creates a coroutine which will execute `fn`. For allocating/deallocating the stack `stack_alloc` is used.]] [[Throws:] [Exceptions thrown inside __coro_fn__.]] ] [heading `~pull_type()`] [variablelist [[Effects:] [Destroys the context and deallocates the stack.]] ] [heading `pull_type( pull_type && other)`] [variablelist [[Effects:] [Moves the internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] [[Throws:] [Nothing.]] ] [heading `pull_type & operator=( pull_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 `explicit operator bool() const noexcept`] [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 noexcept`] [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 `pull_type<> & operator()()`] [variablelist [[Preconditions:] [`*this` is not a __not_a_coro__.]] [[Effects:] [Execution control is transferred to __coro_fn__ (no parameter is passed to the coroutine-function).]] [[Throws:] [Exceptions thrown inside __coro_fn__.]] ] [heading `R get() noexcept`] R coroutine::pull_type::get(); R& coroutine::pull_type::get(); void coroutine::pull_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`]] [[Note:] [If `R` is a move-only type, you may only call `get()` once before the next __pull_coro_op__ call.]] ] [heading Non-member function `begin( pull_type< R > &)`] template< typename R > range_iterator< pull_type< R > >::type begin( pull_type< R > &); [variablelist [[Returns:] [Returns a range-iterator (input-iterator).]] ] [heading Non-member function `end( pull_type< R > &)`] template< typename R > range_iterator< pull_type< R > >::type end( pull_type< R > &); [variablelist [[Returns:] [Returns an end range-iterator (input-iterator).]] [[Note:] [When first obtained from `begin( pull_type< R > &)`, or after some number of increment operations, an iterator will compare equal to the iterator returned by `end( pull_type< R > &)` when the corresponding __pull_coro_bool__ would return `false`.]] ] [endsect] [section:push_coro Class `coroutine<>::push_type`] #include template< typename Arg > class coroutine<>::push_type { public: template< typename Fn > push_type( Fn && fn); template< typename StackAllocator, typename Fn > push_type( StackAllocator stack_alloc, Fn && fn); push_type( push_type const& other)=delete; push_type & operator=( push_type const& other)=delete; ~push_type(); push_type( push_type && other) noexcept; push_type & operator=( push_type && other) noexcept; explicit operator bool() const noexcept; bool operator!() const noexcept; push_type & operator()( Arg arg); }; template< typename Arg > range_iterator< push_type< Arg > >::type begin( push_type< Arg > &); template< typename Arg > range_iterator< push_type< Arg > >::type end( push_type< Arg > &); [heading `template< typename Fn > push_type( Fn && fn)`] [variablelist [[Effects:] [Creates a coroutine which will execute `fn`.]] ] [heading `template< typename StackAllocator, typename Fn > push_type( StackAllocator const& stack_alloc, Fn && fn)`] [variablelist [[Effects:] [Creates a coroutine which will execute `fn`. For allocating/deallocating the stack `stack_alloc` is used.]] ] [heading `~push_type()`] [variablelist [[Effects:] [Destroys the context and deallocates the stack.]] ] [heading `push_type( push_type && other) noexcept`] [variablelist [[Effects:] [Moves the internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] [[Throws:] [Nothing.]] ] [heading `push_type & operator=( push_type && other) noexcept`] [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 `explicit operator bool() const noexcept`] [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 noexcept`] [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 `push_type & operator()(Arg arg)`] push_type& coroutine::push_type::operator()(Arg); push_type& coroutine::push_type::operator()(Arg&); push_type& coroutine::push_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:] [Exceptions thrown inside __coro_fn__.]] ] [heading Non-member function `begin( push_type< Arg > &)`] template< typename Arg > range_iterator< push_type< Arg > >::type begin( push_type< Arg > &); [variablelist [[Returns:] [Returns a range-iterator (output-iterator).]] ] [heading Non-member function `end( push_type< Arg > &)`] template< typename Arg > range_iterator< push_type< Arg > >::type end( push_type< Arg > &); [variablelist [[Returns:] [Returns a end range-iterator (output-iterator).]] [[Note:] [When first obtained from `begin( push_type< R > &)`, or after some number of increment operations, an iterator will compare equal to the iterator returned by `end( push_type< R > &)` when the corresponding __push_coro_bool__ would return `false`.]] ] [endsect] [endsect]