• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1[/
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
6]
7
8[section:symmetric Symmetric coroutine]
9
10In contrast to asymmetric coroutines, where the relationship between caller and
11callee is fixed, symmetric coroutines are able to transfer execution control
12to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required
13to return to its direct caller.
14
15
16[heading __call_coro__]
17__call_coro__ starts a symmetric coroutine and transfers its parameter to its
18__coro_fn__.
19The template parameter defines the transferred parameter type.
20The constructor of __call_coro__ takes a function (__coro_fn__) accepting a
21reference to a __yield_coro__ as argument. Instantiating a __call_coro__ does
22not 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
24__coro_fn__.
25
26The __call_coro__ interface does not contain a ['get()]-function: you can not
27retrieve values from another execution context with this kind of coroutine
28object.
29
30
31[heading __yield_coro__]
32__yield_coro_op__ is used to transfer data and execution control to another
33context by calling __yield_coro_op__ with another __call_coro__ as first argument.
34Alternatively, you may transfer control back to the code that called
35__call_coro_op__ by calling __yield_coro_op__ without a __call_coro__ argument.
36
37The class has only one template parameter defining the transferred parameter
38type.
39Data transferred to the coroutine are accessed through __yield_coro_get__.
40
41[important __yield_coro__ can only be created by the framework.]
42
43        std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b)
44        {
45            std::vector<int> c;
46            std::size_t idx_a=0,idx_b=0;
47            boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0;
48
49            boost::coroutines::symmetric_coroutine<void>::call_type coro_a(
50                [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
51                    while(idx_a<a.size())
52                    {
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
56                    }
57                    // add remaining elements of array b
58                    while ( idx_b < b.size())
59                        c.push_back( b[idx_b++]);
60                });
61
62            boost::coroutines::symmetric_coroutine<void>::call_type coro_b(
63                [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
64                    while(idx_b<b.size())
65                    {
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
69                    }
70                    // add remaining elements of array a
71                    while ( idx_a < a.size())
72                        c.push_back( a[idx_a++]);
73                });
74
75
76            other_a = & coro_a;
77            other_b = & coro_b;
78
79            coro_a(); // enter coroutine-fn of coro_a
80
81            return c;
82        }
83
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);
87        print(a);
88        print(b);
89        print(c);
90
91        output:
92            a : 1 5 6 10
93            b : 2 4 7 8 9 13
94            c : 1 2 4 5 6 7 8 9 10 13
95
96In this example two __call_coro__ are created in the main execution context
97accepting a lambda function (== __coro_fn__) which merges elements of two
98sorted arrays into a third array.
99`coro_a()` enters the __coro_fn__ of `coro_a` cycling through the array and
100testing if the actual element in the other array is less than the element in
101the local one. If so, the coroutine yields to the other coroutine `coro_b`
102using `yield(*other_b)`. If the current element of the local array is less
103than the element of the other array, it is put to the third array.
104Because the coroutine jumps back to `coro_a()` (returning from this method)
105after leaving the __coro_fn__, the elements of the other array will appended
106at the end of the third array if all element of the local array are processed.
107
108
109[heading coroutine-function]
110The __coro_fn__ returns ['void] and takes __yield_coro__, providing
111coroutine functionality inside the __coro_fn__, as argument. Using this
112instance is the only way to transfer data and execution control.
113__call_coro__ does not enter the __coro_fn__ at __call_coro__ construction but
114at the first invocation of __call_coro_op__.
115
116Unless 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.
119
120However, if the template parameter is a move-only type,
121__yield_coro_get__ may only be called once before the next __yield_coro_op__
122call.
123
124[heading passing data from main-context to a symmetric-coroutine]
125In order to transfer data to a __call_coro__ from the main-context the
126framework synthesizes a __yield_coro__ associated with the __call_coro__
127instance. The synthesized __yield_coro__ is passed as argument to __coro_fn__.
128The main-context must call __call_coro_op__ in order to transfer each data value
129into the __coro_fn__.
130Access to the transferred data value is given by __yield_coro_get__.
131
132        boost::coroutines::symmetric_coroutine<int>::call_type coro( // constructor does NOT enter coroutine-function
133            [&](boost::coroutines::symmetric_coroutine<int>::yield_type& yield){
134                for (;;) {
135                    std::cout << yield.get() <<  " ";
136                    yield(); // jump back to starting context
137                 }
138            });
139
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
145
146
147[heading exceptions]
148An uncaught exception inside a __call_coro__'s __coro_fn__ will call
149__terminate__.
150
151[important Code executed by coroutine must not prevent the propagation of the
152__forced_unwind__ exception.  Absorbing that exception will cause stack
153unwinding to fail.  Thus, any code that catches all exceptions must re-throw any
154pending __forced_unwind__ exception.]
155
156        try {
157            // code that might throw
158        } catch(const boost::coroutines::detail::forced_unwind&) {
159            throw;
160        } catch(...) {
161            // possibly not re-throw pending exception
162        }
163
164[important Do not jump from inside a catch block and then re-throw the
165exception in another execution context.]
166
167
168[heading Stack unwinding]
169Sometimes it is necessary to unwind the stack of an unfinished coroutine to
170destroy local stack variables so they can release allocated resources (RAII
171pattern). The `attributes` argument of the coroutine constructor indicates
172whether the destructor should unwind the stack (stack is unwound by default).
173
174Stack unwinding assumes the following preconditions:
175
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
180
181After unwinding, a __coro__ is complete.
182
183        struct X {
184            X(){
185                std::cout<<"X()"<<std::endl;
186            }
187
188            ~X(){
189                std::cout<<"~X()"<<std::endl;
190            }
191        };
192
193        boost::coroutines::symmetric_coroutine<int>::call_type other_coro(...);
194
195        {
196            boost::coroutines::symmetric_coroutine<void>::call_type coro(
197                [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){
198                    X x;
199                    std::cout<<"fn()"<<std::endl;
200                    // transfer execution control to other coroutine
201                    yield( other_coro, 7);
202                });
203
204            coro();
205
206            std::cout<<"coro is complete: "<<std::boolalpha<<!coro<<"\n";
207        }
208
209        output:
210            X()
211            fn()
212            coro is complete: false
213            ~X()
214
215
216[heading Exit a __coro_fn__]
217__coro_fn__ is exited with a simple return statement. This jumps back to the
218calling __call_coro_op__ at the start of symmetric coroutine chain. That is,
219symmetric coroutines do not have a strong, fixed relationship to the caller as
220do asymmetric coroutines. The __call_coro__ becomes complete, e.g.
221__call_coro_bool__ will return `false`.
222
223[important After returning from __coro_fn__ the __coro__ is complete (can not be
224resumed with __call_coro_op__).]
225
226
227[section:symmetric_coro Class `symmetric_coroutine<>::call_type`]
228
229    #include <boost/coroutine/symmetric_coroutine.hpp>
230
231    template< typename Arg >
232    class symmetric_coroutine<>::call_type
233    {
234    public:
235        call_type() noexcept;
236
237        template< typename Fn >
238        call_type( Fn && fn, attributes const& attr = attributes() );
239
240        template< typename Fn, typename StackAllocator >
241        call_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc);
242
243        ~call_type();
244
245        call_type( call_type const& other)=delete;
246
247        call_type & operator=( call_type const& other)=delete;
248
249        call_type( call_type && other) noexcept;
250
251        call_type & operator=( call_type && other) noexcept;
252
253        operator unspecified-bool-type() const;
254
255        bool operator!() const noexcept;
256
257        void swap( call_type & other) noexcept;
258
259        call_type & operator()( Arg arg) noexcept;
260    };
261
262    template< typename Arg >
263    void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
264
265[heading `call_type()`]
266[variablelist
267[[Effects:] [Creates a coroutine representing __not_a_coro__.]]
268[[Throws:] [Nothing.]]
269]
270
271[heading `template< typename Fn >
272          call_type( Fn fn, attributes const& attr)`]
273[variablelist
274[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
275when ! is_stack_unbounded().]]
276[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
277determines stack clean-up.
278For allocating/deallocating the stack `stack_alloc` is used.]]
279]
280
281[heading `template< typename Fn, typename StackAllocator >
282          call_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`]
283[variablelist
284[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
285when ! is_stack_unbounded().]]
286[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
287determines stack clean-up.
288For allocating/deallocating the stack `stack_alloc` is used.]]
289]
290
291[heading `~call_type()`]
292[variablelist
293[[Effects:] [Destroys the context and deallocates the stack.]]
294]
295
296[heading `call_type( call_type && other)`]
297[variablelist
298[[Effects:] [Moves the internal data of `other` to `*this`.
299`other` becomes __not_a_coro__.]]
300[[Throws:] [Nothing.]]
301]
302
303[heading `call_type & operator=( call_type && other)`]
304[variablelist
305[[Effects:] [Destroys the internal data of `*this` and moves the
306internal data of `other` to `*this`. `other` becomes __not_a_coro__.]]
307[[Throws:] [Nothing.]]
308]
309
310[heading `operator unspecified-bool-type() const`]
311[variablelist
312[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
313has returned (completed), the function returns `false`. Otherwise `true`.]]
314[[Throws:] [Nothing.]]
315]
316
317[heading `bool operator!() const`]
318[variablelist
319[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
320has returned (completed), the function returns `true`. Otherwise `false`.]]
321[[Throws:] [Nothing.]]
322]
323
324[heading `void swap( call_type & other)`]
325[variablelist
326[[Effects:] [Swaps the internal data from `*this` with the values
327of `other`.]]
328[[Throws:] [Nothing.]]
329]
330
331[heading `call_type & operator()(Arg arg)`]
332
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()();
336
337[variablelist
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.]]
342]
343
344[heading Non-member function `swap()`]
345
346    template< typename Arg >
347    void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
348
349[variablelist
350[[Effects:] [As if 'l.swap( r)'.]]
351]
352
353[endsect]
354
355
356
357[section:yield_coro Class `symmetric_coroutine<>::yield_type`]
358
359    #include <boost/coroutine/symmetric_coroutine.hpp>
360
361    template< typename R >
362    class symmetric_coroutine<>::yield_type
363    {
364    public:
365        yield_type() noexcept;
366
367        yield_type( yield_type const& other)=delete;
368
369        yield_type & operator=( yield_type const& other)=delete;
370
371        yield_type( yield_type && other) noexcept;
372
373        yield_type & operator=( yield_type && other) noexcept;
374
375        void swap( yield_type & other) noexcept;
376
377        operator unspecified-bool-type() const;
378
379        bool operator!() const noexcept;
380
381        yield_type & operator()();
382
383        template< typename X >
384        yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
385
386        template< typename X >
387        yield_type & operator()( symmetric_coroutine< X >::call_type & other);
388
389        R get() const;
390    };
391
392[heading `operator unspecified-bool-type() const`]
393[variablelist
394[[Returns:] [If `*this` refers to __not_a_coro__, the function returns `false`.
395Otherwise `true`.]]
396[[Throws:] [Nothing.]]
397]
398
399[heading `bool operator!() const`]
400[variablelist
401[[Returns:] [If `*this` refers to __not_a_coro__, the function returns `true`.
402Otherwise `false`.]]
403[[Throws:] [Nothing.]]
404]
405
406[heading `yield_type & operator()()`]
407
408        yield_type & operator()();
409        template< typename X >
410        yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
411        template<>
412        yield_type & operator()( symmetric_coroutine< void >::call_type & other);
413
414[variablelist
415[[Preconditions:] [`*this` is not a __not_a_coro__.]]
416[[Effects:] [The first function transfers execution control back to the starting point,
417e.g. invocation of __call_coro_op__. The last two functions transfer the execution control
418to another symmetric coroutine. Parameter `x` is passed as value into `other`'s context.]]
419[[Throws:] [__forced_unwind__]]
420]
421
422[heading `R get()`]
423
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;
427
428[variablelist
429[[Preconditions:] [`*this` is not a __not_a_coro__.]]
430[[Returns:] [Returns data transferred from coroutine-function via
431__call_coro_op__.]]
432[[Throws:] [`invalid_result`]]
433]
434
435[endsect]
436
437
438
439[endsect]
440