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
8 [section:symmetric Symmetric coroutine]
10 In contrast to asymmetric coroutines, where the relationship between caller and
11 callee is fixed, symmetric coroutines are able to transfer execution control
12 to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required
13 to return to its direct caller.
16 [heading __call_coro__]
17 __call_coro__ starts a symmetric coroutine and transfers its parameter to its
19 The template parameter defines the transferred parameter type.
20 The constructor of __call_coro__ takes a function (__coro_fn__) accepting a
21 reference to a __yield_coro__ as argument. Instantiating a __call_coro__ does
22 not 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
26 The __call_coro__ interface does not contain a ['get()]-function: you can not
27 retrieve values from another execution context with this kind of coroutine
31 [heading __yield_coro__]
32 __yield_coro_op__ is used to transfer data and execution control to another
33 context by calling __yield_coro_op__ with another __call_coro__ as first argument.
34 Alternatively, you may transfer control back to the code that called
35 __call_coro_op__ by calling __yield_coro_op__ without a __call_coro__ argument.
37 The class has only one template parameter defining the transferred parameter
39 Data transferred to the coroutine are accessed through __yield_coro_get__.
41 [important __yield_coro__ can only be created by the framework.]
43 std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b)
46 std::size_t idx_a=0,idx_b=0;
47 boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0;
49 boost::coroutines::symmetric_coroutine<void>::call_type coro_a(
50 [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
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
57 // add remaining elements of array b
58 while ( idx_b < b.size())
59 c.push_back( b[idx_b++]);
62 boost::coroutines::symmetric_coroutine<void>::call_type coro_b(
63 [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
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
70 // add remaining elements of array a
71 while ( idx_a < a.size())
72 c.push_back( a[idx_a++]);
79 coro_a(); // enter coroutine-fn of coro_a
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);
94 c : 1 2 4 5 6 7 8 9 10 13
96 In this example two __call_coro__ are created in the main execution context
97 accepting a lambda function (== __coro_fn__) which merges elements of two
98 sorted arrays into a third array.
99 `coro_a()` enters the __coro_fn__ of `coro_a` cycling through the array and
100 testing if the actual element in the other array is less than the element in
101 the local one. If so, the coroutine yields to the other coroutine `coro_b`
102 using `yield(*other_b)`. If the current element of the local array is less
103 than the element of the other array, it is put to the third array.
104 Because the coroutine jumps back to `coro_a()` (returning from this method)
105 after leaving the __coro_fn__, the elements of the other array will appended
106 at the end of the third array if all element of the local array are processed.
109 [heading coroutine-function]
110 The __coro_fn__ returns ['void] and takes __yield_coro__, providing
111 coroutine functionality inside the __coro_fn__, as argument. Using this
112 instance is the only way to transfer data and execution control.
113 __call_coro__ does not enter the __coro_fn__ at __call_coro__ construction but
114 at the first invocation of __call_coro_op__.
116 Unless 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.
120 However, if the template parameter is a move-only type,
121 __yield_coro_get__ may only be called once before the next __yield_coro_op__
124 [heading passing data from main-context to a symmetric-coroutine]
125 In order to transfer data to a __call_coro__ from the main-context the
126 framework synthesizes a __yield_coro__ associated with the __call_coro__
127 instance. The synthesized __yield_coro__ is passed as argument to __coro_fn__.
128 The main-context must call __call_coro_op__ in order to transfer each data value
129 into the __coro_fn__.
130 Access to the transferred data value is given by __yield_coro_get__.
132 boost::coroutines::symmetric_coroutine<int>::call_type coro( // constructor does NOT enter coroutine-function
133 [&](boost::coroutines::symmetric_coroutine<int>::yield_type& yield){
135 std::cout << yield.get() << " ";
136 yield(); // jump back to starting context
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
148 An uncaught exception inside a __call_coro__'s __coro_fn__ will call
151 [important Code executed by coroutine must not prevent the propagation of the
152 __forced_unwind__ exception. Absorbing that exception will cause stack
153 unwinding to fail. Thus, any code that catches all exceptions must re-throw any
154 pending __forced_unwind__ exception.]
157 // code that might throw
158 } catch(const boost::coroutines::detail::forced_unwind&) {
161 // possibly not re-throw pending exception
164 [important Do not jump from inside a catch block and then re-throw the
165 exception in another execution context.]
168 [heading Stack unwinding]
169 Sometimes it is necessary to unwind the stack of an unfinished coroutine to
170 destroy local stack variables so they can release allocated resources (RAII
171 pattern). The `attributes` argument of the coroutine constructor indicates
172 whether the destructor should unwind the stack (stack is unwound by default).
174 Stack unwinding assumes the following preconditions:
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
181 After unwinding, a __coro__ is complete.
185 std::cout<<"X()"<<std::endl;
189 std::cout<<"~X()"<<std::endl;
193 boost::coroutines::symmetric_coroutine<int>::call_type other_coro(...);
196 boost::coroutines::symmetric_coroutine<void>::call_type coro(
197 [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){
199 std::cout<<"fn()"<<std::endl;
200 // transfer execution control to other coroutine
201 yield( other_coro, 7);
206 std::cout<<"coro is complete: "<<std::boolalpha<<!coro<<"\n";
212 coro is complete: false
216 [heading Exit a __coro_fn__]
217 __coro_fn__ is exited with a simple return statement. This jumps back to the
218 calling __call_coro_op__ at the start of symmetric coroutine chain. That is,
219 symmetric coroutines do not have a strong, fixed relationship to the caller as
220 do asymmetric coroutines. The __call_coro__ becomes complete, e.g.
221 __call_coro_bool__ will return `false`.
223 [important After returning from __coro_fn__ the __coro__ is complete (can not be
224 resumed with __call_coro_op__).]
227 [section:symmetric_coro Class `symmetric_coroutine<>::call_type`]
229 #include <boost/coroutine/symmetric_coroutine.hpp>
231 template< typename Arg >
232 class symmetric_coroutine<>::call_type
235 call_type() noexcept;
237 template< typename Fn >
238 call_type( Fn && fn, attributes const& attr = attributes() );
240 template< typename Fn, typename StackAllocator >
241 call_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc);
245 call_type( call_type const& other)=delete;
247 call_type & operator=( call_type const& other)=delete;
249 call_type( call_type && other) noexcept;
251 call_type & operator=( call_type && other) noexcept;
253 operator unspecified-bool-type() const;
255 bool operator!() const noexcept;
257 void swap( call_type & other) noexcept;
259 call_type & operator()( Arg arg) noexcept;
262 template< typename Arg >
263 void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
265 [heading `call_type()`]
267 [[Effects:] [Creates a coroutine representing __not_a_coro__.]]
268 [[Throws:] [Nothing.]]
271 [heading `template< typename Fn >
272 call_type( Fn fn, attributes const& attr)`]
274 [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
275 when ! is_stack_unbounded().]]
276 [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
277 determines stack clean-up.
278 For allocating/deallocating the stack `stack_alloc` is used.]]
281 [heading `template< typename Fn, typename StackAllocator >
282 call_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`]
284 [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
285 when ! is_stack_unbounded().]]
286 [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
287 determines stack clean-up.
288 For allocating/deallocating the stack `stack_alloc` is used.]]
291 [heading `~call_type()`]
293 [[Effects:] [Destroys the context and deallocates the stack.]]
296 [heading `call_type( call_type && other)`]
298 [[Effects:] [Moves the internal data of `other` to `*this`.
299 `other` becomes __not_a_coro__.]]
300 [[Throws:] [Nothing.]]
303 [heading `call_type & operator=( call_type && other)`]
305 [[Effects:] [Destroys the internal data of `*this` and moves the
306 internal data of `other` to `*this`. `other` becomes __not_a_coro__.]]
307 [[Throws:] [Nothing.]]
310 [heading `operator unspecified-bool-type() const`]
312 [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
313 has returned (completed), the function returns `false`. Otherwise `true`.]]
314 [[Throws:] [Nothing.]]
317 [heading `bool operator!() const`]
319 [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
320 has returned (completed), the function returns `true`. Otherwise `false`.]]
321 [[Throws:] [Nothing.]]
324 [heading `void swap( call_type & other)`]
326 [[Effects:] [Swaps the internal data from `*this` with the values
328 [[Throws:] [Nothing.]]
331 [heading `call_type & operator()(Arg arg)`]
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()();
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.]]
344 [heading Non-member function `swap()`]
346 template< typename Arg >
347 void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
350 [[Effects:] [As if 'l.swap( r)'.]]
357 [section:yield_coro Class `symmetric_coroutine<>::yield_type`]
359 #include <boost/coroutine/symmetric_coroutine.hpp>
361 template< typename R >
362 class symmetric_coroutine<>::yield_type
365 yield_type() noexcept;
367 yield_type( yield_type const& other)=delete;
369 yield_type & operator=( yield_type const& other)=delete;
371 yield_type( yield_type && other) noexcept;
373 yield_type & operator=( yield_type && other) noexcept;
375 void swap( yield_type & other) noexcept;
377 operator unspecified-bool-type() const;
379 bool operator!() const noexcept;
381 yield_type & operator()();
383 template< typename X >
384 yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
386 template< typename X >
387 yield_type & operator()( symmetric_coroutine< X >::call_type & other);
392 [heading `operator unspecified-bool-type() const`]
394 [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `false`.
396 [[Throws:] [Nothing.]]
399 [heading `bool operator!() const`]
401 [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `true`.
403 [[Throws:] [Nothing.]]
406 [heading `yield_type & operator()()`]
408 yield_type & operator()();
409 template< typename X >
410 yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
412 yield_type & operator()( symmetric_coroutine< void >::call_type & other);
415 [[Preconditions:] [`*this` is not a __not_a_coro__.]]
416 [[Effects:] [The first function transfers execution control back to the starting point,
417 e.g. invocation of __call_coro_op__. The last two functions transfer the execution control
418 to another symmetric coroutine. Parameter `x` is passed as value into `other`'s context.]]
419 [[Throws:] [__forced_unwind__]]
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;
429 [[Preconditions:] [`*this` is not a __not_a_coro__.]]
430 [[Returns:] [Returns data transferred from coroutine-function via
432 [[Throws:] [`invalid_result`]]