123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- [/
- 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 then 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.
- 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.
- 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
- __call_coro_op__.]]
- [[Throws:] [`invalid_result`]]
- ]
- [endsect]
- [endsect]
|