123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- [/
- 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<int> 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<std::string> 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<num;++i){
- // when we exhaust the input, stop
- if(!in) return;
- std::cout << std::setw(width) << in.get();
- // now that we've handled this item, advance to next
- in();
- }
- // after 'num' items, line break
- std::cout << std::endl;
- }
- });
- std::vector<std::string> 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<int> 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<int> 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<int> 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<boost::tuple<int,int>> 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()"<<std::endl;
- }
- ~X(){
- std::cout<<"~X()"<<std::endl;
- }
- };
- {
- typedef boost::coroutines2::coroutine<void>::push_type coro_t;
- coro_t::push_type sink(
- [&](coro_t::pull_type& source){
- X x;
- for(int=0;;++i){
- std::cout<<"fn(): "<<i<<std::endl;
- // transfer execution control back to main()
- source();
- }
- });
- sink();
- sink();
- sink();
- sink();
- sink();
- std::cout<<"sink is complete: "<<std::boolalpha<<!sink<<"\n";
- }
- output:
- X()
- fn(): 0
- fn(): 1
- fn(): 2
- fn(): 3
- fn(): 4
- fn(): 5
- sink is complete: false
- ~X()
- [heading Range iterators]
- __boost_coroutine__ provides output- and input-iterators using __boost_range__.
- __pull_coro__ can be used via input-iterators using __begin__ and __end__.
- typedef boost::coroutines2::coroutine< int > coro_t;
- int number=2,exponent=8;
- coro_t::pull_type source(
- [&](coro_t::push_type & sink){
- int counter=0,result=1;
- while(counter++<exponent){
- result=result*number;
- sink(result);
- }
- });
- for (auto i:source)
- std::cout << i << " ";
- output:
- 2 4 8 16 32 64 128 256
- ['coroutine<>::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<T>::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<int> coro_t;
- coro_t::push_type sink(
- [&](coro_t::pull_type& source){
- while(source){
- std::cout << source.get() << " ";
- source();
- }
- });
- std::vector<int> 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 <boost/coroutine2/coroutine.hpp>
- 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<R,StackAllocator>::pull_type::get();
- R& coroutine<R&,StackAllocator>::pull_type::get();
- void coroutine<void,StackAllocator>::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 <boost/coroutine2/coroutine.hpp>
- 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<Arg>::push_type::operator()(Arg);
- push_type& coroutine<Arg&>::push_type::operator()(Arg&);
- push_type& coroutine<void>::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]
|