]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | [/ |
2 | Copyright Oliver Kowalke 2014. | |
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 | [#ecv2] | |
9 | [section:ecv2 Class execution_context (version 2)] | |
10 | ||
11 | [note This class is enabled per default.] | |
12 | ||
13 | Class __econtext__ encapsulates context switching and manages the associated | |
14 | context' stack (allocation/deallocation). | |
15 | ||
16 | __econtext__ allocates the context stack (using its [link stack | |
17 | __stack_allocator__] argument) and creates a control structure on top of it. | |
18 | This structure is responsible for managing context' stack. The address of the | |
19 | control structure is stored in the first frame of context' stack (e.g. it can | |
20 | not directly accessed from within __econtext__). In contrast to __ecv1__ the | |
21 | ownership of the control structure is not shared (no member variable to control | |
22 | structure in __econtext__). __econtext__ keeps internally a state that is moved | |
23 | by a call of __ec_op__ (`*this` will be invalidated), e.g. after a calling | |
24 | __ec_op__, `*this` can not be used for an additional context switch. | |
25 | ||
26 | __econtext__ is only move-constructible and move-assignable. | |
27 | ||
28 | The moved state is assigned to a new instance of __econtext__. This object | |
29 | becomes the first argument of the context-function, if the context was resumed | |
30 | the first time, or the first element in a tuple returned by __ec_op__ that has | |
31 | been called in the resumed context. | |
32 | In contrast to __ecv1__, the context switch is faster because no global pointer | |
33 | etc. is involved. | |
34 | ||
35 | [important Segmented stacks are not supported by __econtext__ (v2).] | |
36 | ||
37 | On return the context-function of the current context has to specify an | |
38 | __econtext__ to which the execution control is transferred after termination | |
39 | of the current context. | |
40 | ||
41 | If an instance with valid state goes out of scope and the context-function has | |
42 | not yet returned, the stack is traversed in order to access the control | |
43 | structure (address stored at the first stack frame) and context' stack is | |
44 | deallocated via the __stack_allocator__. The stack walking makes the destruction | |
45 | of __econtext__ slow and should be prevented if possible. | |
46 | ||
47 | __econtext__ expects a __context_fn__ with signature | |
48 | `execution_context(execution_context ctx, Args ... args)`. The parameter `ctx` | |
49 | represents the context from which this context was resumed (e.g. that has called | |
50 | __ec_op__ on `*this`) and `args` are the data passed to __ec_op__. The return | |
51 | value represents the execution_context that has to be resumed, after termiantion | |
52 | of this context. | |
53 | ||
54 | Benefits of __ecv2__ over __ecv1__ are: faster context switch, type-safety of | |
55 | passed/returned arguments. | |
56 | ||
57 | ||
58 | [heading usage of __econtext__] | |
59 | ||
60 | int n=35; | |
61 | ctx::execution_context<int> source( | |
62 | [n](ctx::execution_context<int> sink, int) mutable { | |
63 | int a=0; | |
64 | int b=1; | |
65 | while(n-->0){ | |
66 | auto result=sink(a); | |
67 | sink=std::move(std::get<0>(result)); | |
68 | auto next=a+b; | |
69 | a=b; | |
70 | b=next; | |
71 | } | |
72 | return sink; | |
73 | }); | |
74 | for(int i=0;i<10;++i){ | |
75 | auto result=source(i); | |
76 | source=std::move(std::get<0>(result)); | |
77 | std::cout<<std::get<1>(result)<<" "; | |
78 | } | |
79 | ||
80 | output: | |
81 | 0 1 1 2 3 5 8 13 21 34 | |
82 | ||
83 | This simple example demonstrates the basic usage of __econtext__ as a generator. | |
84 | The context `sink` represents the ['main]-context (function ['main()] running). | |
85 | `sink` is generated by the framework (first element of lambda's parameter list). | |
86 | Because the state is invalidated (== changed) by each call of __ec_op__, the new | |
87 | state of the __econtext__, returned by __ec_op__, needs to be assigned to `sink` | |
88 | after each call. | |
89 | ||
90 | The lambda that calculates the Fibonacci numbers is executed inside | |
91 | the context represented by `source`. Calculated Fibonacci numbers are | |
92 | transferred between the two context' via expression ['sink(a)] (and returned by | |
93 | ['source()]). Note that this example represents a ['generator] thus the value | |
94 | transferred into the lambda via ['source()] is not used. Using | |
95 | ['boost::optional<>] as transferred type, might also appropriate to express this | |
96 | fact. | |
97 | ||
98 | The locale variables `a`, `b` and ` next` remain their values during each | |
99 | context switch (['yield(a)]). This is possible due `source` has its own stack | |
100 | and the stack is exchanged by each context switch. | |
101 | ||
102 | ||
103 | [heading parameter passing] | |
104 | With `execution_context<void>` no data will be transferred, only the context | |
105 | switch is executed. | |
106 | ||
107 | boost::context::execution_context<void> ctx1([](boost::context::execution_context<void> ctx2){ | |
108 | std::printf("inside ctx1\n"); | |
109 | return ctx2(); | |
110 | }); | |
111 | ctx1(); | |
112 | ||
113 | output: | |
114 | inside ctx1 | |
115 | ||
116 | `ctx1()` resumes `ctx1`, e.g. the lambda passed at the constructor of `ctx1` is | |
117 | entered. Argument `ctx2` represents the context that has been suspended with the | |
118 | invocation of `ctx1()`. When the lambda returns `ctx2`, context `ctx1` will be | |
119 | terminated while the context represented by `ctx2` is resumed, hence the control | |
120 | of execution returns from `ctx1()`. | |
121 | ||
122 | The arguments passed to __ec_op__, in one context, is passed as the last | |
123 | arguments of the __context_fn__ if the context is started for the first time. | |
124 | In all following invocations of __ec_op__ the arguments passed to __ec_op__, in | |
125 | one context, is returned by __ec_op__ in the other context. | |
126 | ||
127 | boost::context::execution_context<int> ctx1([](boost::context::execution_context<int> ctx2, int j){ | |
128 | std::printf("inside ctx1, j == %d\n", j); | |
129 | return ctx2(j+1); | |
130 | }); | |
131 | int i = 1; | |
132 | std::tie(ctx1, i) = ctx1(i); | |
133 | std::printf("i == %d\n", i); | |
134 | ||
135 | output: | |
136 | inside ctx1, j == 1 | |
137 | i == 2 | |
138 | ||
139 | `ctx1(i)` enters the lambda in context `ctx1` with argument `j=1`. The | |
140 | expression `ctx2(j+1)` resumes the context represented by `ctx2` and transfers | |
141 | back an integer of `j+1`. On return of `ctx1(i)`, the variable `i` contains the | |
142 | value of `j+1`. | |
143 | ||
144 | If more than one argument has to be transferred, the signature of the | |
145 | context-function is simply extended. | |
146 | ||
147 | boost::context::execution_context<int,int> ctx1([](boost::context::execution_context<int,int> ctx2, int i, int j){ | |
148 | std::printf("inside ctx1, i == %d j == %d\n", i, j); | |
149 | return ctx2(i+j,i-j); | |
150 | }); | |
151 | int i = 2, j = 1; | |
152 | std::tie(ctx1, i, j) = ctx1(i,j); | |
153 | std::printf("i == %d j == %d\n", i, j); | |
154 | ||
155 | output: | |
156 | inside ctx1, i == 2 j == 1 | |
157 | i == 3 j == 1 | |
158 | ||
159 | For use-cases, that require to transfer data of different type in each | |
160 | direction, ['boost::variant<>] could be used. | |
161 | ||
162 | class X{ | |
163 | private: | |
164 | std::exception_ptr excptr_; | |
165 | boost::context::execution_context<boost::variant<int,std::string>> ctx_; | |
166 | ||
167 | public: | |
168 | X(): | |
169 | excptr_(), | |
170 | ctx_( | |
171 | [=](boost::context::execution_context<boost::variant<int,std::string>> ctx, boost::variant<int,std::string> data){ | |
172 | try { | |
173 | for (;;) { | |
174 | int i = boost::get<int>(data); | |
175 | data = boost::lexical_cast<std::string>(i); | |
176 | auto result = ctx( data); | |
177 | ctx = std::move( std::get<0>( result) ); | |
178 | data = std::get<1>( result); | |
179 | } catch (std::bad_cast const&) { | |
180 | excptr_=std::current_exception(); | |
181 | } | |
182 | return ctx; | |
183 | }) | |
184 | {} | |
185 | ||
186 | std::string operator()(int i){ | |
187 | boost::variant<int,std::string> data = i; | |
188 | auto result = ctx_( data); | |
189 | ctx_ = std::move( std::get<0>( result) ); | |
190 | data = std::get<1>( result); | |
191 | if(excptr_){ | |
192 | std::rethrow_exception(excptr_); | |
193 | } | |
194 | return boost::get<std::string>(data); | |
195 | } | |
196 | }; | |
197 | ||
198 | X x; | |
199 | std::cout << x( 7) << std::endl; | |
200 | ||
201 | output: | |
202 | 7 | |
203 | ||
204 | In the case of unidirectional transfer of data, ['boost::optional<>] or a | |
205 | pointer are appropriate. | |
206 | ||
207 | ||
208 | [heading exception handling] | |
209 | If the function executed inside a __econtext__ emits ans exception, the | |
210 | application is terminated by calling ['std::terminate()]. ['std::exception_ptr] | |
211 | can be used to transfer exceptions between different execution contexts. | |
212 | ||
213 | [important Do not jump from inside a catch block and then re-throw the exception | |
214 | in another execution context.] | |
215 | ||
216 | [#ecv2_ontop] | |
217 | [heading Executing function on top of a context] | |
218 | Sometimes it is useful to execute a new function on top of a resumed context. | |
219 | For this purpose __ec_op__ with first argument `exec_ontop_arg` has to be used. | |
220 | The function passed as argument must return a tuple of execution_context and | |
221 | arguments. | |
222 | ||
223 | boost::context::execution_context<int> f1(boost::context::execution_context<int> ctx,int data) { | |
224 | std::cout << "f1: entered first time: " << data << std::endl; | |
225 | std::tie(ctx,data) = ctx(data+1); | |
226 | std::cout << "f1: entered second time: " << data << std::endl; | |
227 | std::tie(ctx,data) = ctx(data+1); | |
228 | std::cout << "f1: entered third time: " << data << std::endl; | |
229 | return ctx; | |
230 | } | |
231 | ||
232 | std::tuple<boost::context::execution_context<int>,int> f2(boost::context::execution_context<int> ctx,int data) { | |
233 | std::cout << "f2: entered: " << data << std::endl; | |
234 | return std::make_tuple(std::move(ctx),-1); | |
235 | } | |
236 | ||
237 | int data = 0; | |
238 | ctx::execution_context< int > ctx(f1); | |
239 | std::tie(ctx,data) = ctx(data+1); | |
240 | std::cout << "f1: returned first time: " << data << std::endl; | |
241 | std::tie(ctx,data) = ctx(data+1); | |
242 | std::cout << "f1: returned second time: " << data << std::endl; | |
243 | std::tie(ctx,data) = ctx(ctx::exec_ontop_arg,f2,data+1); | |
244 | ||
245 | output: | |
246 | f1: entered first time: 1 | |
247 | f1: returned first time: 2 | |
248 | f1: entered second time: 3 | |
249 | f1: returned second time: 4 | |
250 | f2: entered: 5 | |
251 | f1: entered third time: -1 | |
252 | ||
253 | The expression `ctx(ctx::exec_ontop_arg,f2,data+1)` executes `f2()` on top of | |
254 | context `ctx`, e.g. an additional stack frame is allocated on top of the context | |
255 | stack (in front of `f1()`). `f2()` returns argument `-1` that will returned by | |
256 | the second invocation of `ctx(data+1)` in `f1()`. | |
257 | ||
258 | Another option is to execute a function on top of the context that throws an | |
259 | exception. | |
260 | ||
261 | struct interrupt { | |
262 | boost::context::execution_context< void > ctx; | |
263 | ||
264 | interrupt( boost::context::execution_context< void > && ctx_) : | |
265 | ctx( std::forward< boost::context::execution_context< void > >( ctx_) ) { | |
266 | } | |
267 | }; | |
268 | ||
269 | boost::context::execution_context<void> f1(boost::context::execution_context<void> ctx) { | |
270 | try { | |
271 | for (;;) { | |
272 | std::cout << "f1()" << std::endl; | |
273 | ctx = ctx(); | |
274 | } | |
275 | } catch (interrupt & e) { | |
276 | std::cout << "f1(): interrupted" << std::endl; | |
277 | ctx = std::move( e.ctx); | |
278 | } | |
279 | return ctx; | |
280 | } | |
281 | ||
282 | boost::context::execution_context<void> f2(boost::context::execution_context<void> ctx) { | |
283 | throw interrupt(std::move(ctx)); | |
284 | return ctx; | |
285 | } | |
286 | ||
287 | boost::context::execution_context< void > ctx(f1); | |
288 | ctx = ctx(); | |
289 | ctx = ctx(); | |
290 | ctx = ctx(boost::context::exec_ontop_arg,f2); | |
291 | ||
292 | output: | |
293 | f1() | |
294 | f1() | |
295 | f1(): interrupted | |
296 | ||
297 | In this example `f2()` is used to interrupt the `for`-loop in `f1()`. | |
298 | ||
299 | ||
300 | [heading stack unwinding] | |
301 | On construction of __econtext__ a stack is allocated. | |
302 | If the __context_fn__ returns the stack will be destructed. | |
303 | If the __context_fn__ has not yet returned and the destructor of an valid | |
304 | __econtext__ instance (e.g. ['execution_context::operator bool()] returns | |
305 | `true`) is called, the stack will be destructed too. | |
306 | ||
307 | [important Code executed by __context_fn__ must not prevent the propagation of the | |
308 | __forced_unwind__ exception. Absorbing that exception will cause stack | |
309 | unwinding to fail. Thus, any code that catches all exceptions must re-throw any | |
310 | pending __forced_unwind__ exception.] | |
311 | ||
312 | ||
313 | [#ecv2_prealloc] | |
314 | [heading allocating control structures on top of stack] | |
315 | Allocating control structures on top of the stack requires to allocated the | |
316 | __stack_context__ and create the control structure with placement new before | |
317 | __econtext__ is created. | |
318 | [note The user is responsible for destructing the control structure at the top | |
319 | of the stack.] | |
320 | ||
321 | // stack-allocator used for (de-)allocating stack | |
322 | fixedsize_stack salloc( 4048); | |
323 | // allocate stack space | |
324 | stack_context sctx( salloc.allocate() ); | |
325 | // reserve space for control structure on top of the stack | |
326 | void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure); | |
327 | std::size_t size = sctx.size - sizeof( my_control_structure); | |
328 | // placement new creates control structure on reserved space | |
329 | my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc); | |
330 | ... | |
331 | // destructing the control structure | |
332 | cs->~my_control_structure(); | |
333 | ... | |
334 | struct my_control_structure { | |
335 | // captured context | |
336 | execution_context cctx; | |
337 | ||
338 | template< typename StackAllocator > | |
339 | my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) : | |
340 | // create captured context | |
341 | cctx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) { | |
342 | } | |
343 | ... | |
344 | }; | |
345 | ||
346 | ||
347 | [heading inverting the control flow] | |
348 | ||
349 | /* | |
350 | * grammar: | |
351 | * P ---> E '\0' | |
352 | * E ---> T {('+'|'-') T} | |
353 | * T ---> S {('*'|'/') S} | |
354 | * S ---> digit | '(' E ')' | |
355 | */ | |
356 | class Parser{ | |
357 | // implementation omitted; see examples directory | |
358 | }; | |
359 | ||
360 | std::istringstream is("1+1"); | |
361 | bool done=false; | |
362 | std::exception_ptr except; | |
363 | ||
364 | // execute parser in new execution context | |
365 | boost::context::execution_context<char> source( | |
366 | [&is,&done,&except](ctx::execution_context<char> sink,char){ | |
367 | // create parser with callback function | |
368 | Parser p( is, | |
369 | [&sink](char ch){ | |
370 | // resume main execution context | |
371 | auto result = sink(ch); | |
372 | sink = std::move(std::get<0>(result)); | |
373 | }); | |
374 | try { | |
375 | // start recursive parsing | |
376 | p.run(); | |
377 | } catch (...) { | |
378 | // store other exceptions in exception-pointer | |
379 | except = std::current_exception(); | |
380 | } | |
381 | // set termination flag | |
382 | done=true; | |
383 | // resume main execution context | |
384 | return sink; | |
385 | }); | |
386 | ||
387 | // user-code pulls parsed data from parser | |
388 | // invert control flow | |
389 | auto result = source('\0'); | |
390 | source = std::move(std::get<0>(result)); | |
391 | char c = std::get<1>(result); | |
392 | if ( except) { | |
393 | std::rethrow_exception(except); | |
394 | } | |
395 | while( ! done) { | |
396 | printf("Parsed: %c\n",c); | |
397 | std::tie(source,c) = source('\0'); | |
398 | if (except) { | |
399 | std::rethrow_exception(except); | |
400 | } | |
401 | } | |
402 | ||
403 | output: | |
404 | Parsed: 1 | |
405 | Parsed: + | |
406 | Parsed: 1 | |
407 | ||
408 | In this example a recursive descent parser uses a callback to emit a newly | |
409 | passed symbol. Using __econtext__ the control flow can be inverted, e.g. the | |
410 | user-code pulls parsed symbols from the parser - instead to get pushed from the | |
411 | parser (via callback). | |
412 | ||
413 | The data (character) is transferred between the two __econtext__. | |
414 | ||
415 | If the code executed by __econtext__ emits an exception, the application is | |
416 | terminated. ['std::exception_ptr] can be used to transfer exceptions between | |
417 | different execution contexts. | |
418 | ||
419 | Sometimes it is necessary to unwind the stack of an unfinished context to | |
420 | destroy local stack variables so they can release allocated resources (RAII | |
421 | pattern). The user is responsible for this task. | |
422 | ||
423 | ||
424 | [heading Class `execution_context`] | |
425 | ||
426 | struct exec_ontop_arg_t {}; | |
427 | const exec_ontop_arg_t exec_ontop_arg{}; | |
428 | ||
429 | template< typename ... Args > | |
430 | class execution_context { | |
431 | public: | |
432 | template< typename Fn, typename ... Params > | |
433 | execution_context( Fn && fn, Params && ... params); | |
434 | ||
435 | template< typename StackAlloc, typename Fn, typename ... Params > | |
436 | execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params); | |
437 | ||
438 | template< typename StackAlloc, typename Fn, typename ... Params > | |
439 | execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params); | |
440 | ||
441 | template< typename Fn, typename ... Params > | |
442 | execution_context( std::allocator_arg_t, segemented_stack, Fn && fn, Params && ... params) = delete; | |
443 | ||
444 | template< typename Fn, typename ... Params > | |
445 | execution_context( std::allocator_arg_t, preallocated palloc, segmented, Fn && fn, Params && ... params)= delete; | |
446 | ||
447 | ~execution_context(); | |
448 | ||
449 | execution_context( execution_context && other) noexcept; | |
450 | execution_context & operator=( execution_context && other) noexcept; | |
451 | ||
452 | execution_context( execution_context const& other) noexcept = delete; | |
453 | execution_context & operator=( execution_context const& other) noexcept = delete; | |
454 | ||
455 | explicit operator bool() const noexcept; | |
456 | bool operator!() const noexcept; | |
457 | ||
458 | std::tuple< execution_context, Args ... > operator()( Args ... args); | |
459 | ||
460 | template< typename Fn > | |
461 | std::tuple< execution_context, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); | |
462 | ||
463 | bool operator==( execution_context const& other) const noexcept; | |
464 | ||
465 | bool operator!=( execution_context const& other) const noexcept; | |
466 | ||
467 | bool operator<( execution_context const& other) const noexcept; | |
468 | ||
469 | bool operator>( execution_context const& other) const noexcept; | |
470 | ||
471 | bool operator<=( execution_context const& other) const noexcept; | |
472 | ||
473 | bool operator>=( execution_context const& other) const noexcept; | |
474 | ||
475 | template< typename charT, class traitsT > | |
476 | friend std::basic_ostream< charT, traitsT > & | |
477 | operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); | |
478 | }; | |
479 | ||
480 | [constructor_heading ecv2..constructor] | |
481 | ||
482 | template< typename Fn, typename ... Params > | |
483 | execution_context( Fn && fn, Params && ... params); | |
484 | ||
485 | template< typename StackAlloc, typename Fn, typename ... Params > | |
486 | execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params); | |
487 | ||
488 | template< typename StackAlloc, typename Fn, typename ... Params > | |
489 | execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params); | |
490 | ||
491 | [variablelist | |
492 | [[Effects:] [Creates a new execution context and prepares the context to execute | |
493 | `fn`. `fixedsize_stack` is used as default stack allocator | |
494 | (stack size == fixedsize_stack::traits::default_size()). | |
495 | The constructor with argument type `preallocated`, is used to create a user | |
496 | defined data [link ecv2_prealloc (for instance additional control structures)] on | |
497 | top of the stack.]] | |
498 | ] | |
499 | [destructor_heading ecv2..destructor destructor] | |
500 | ||
501 | ~execution_context(); | |
502 | ||
503 | [variablelist | |
504 | [[Effects:] [Destructs the associated stack if `*this` is a valid context, | |
505 | e.g. ['execution_context::operator bool()] returns `true`.]] | |
506 | [[Throws:] [Nothing.]] | |
507 | ] | |
508 | ||
509 | [move_constructor_heading ecv2..move constructor] | |
510 | ||
511 | execution_context( execution_context && other) noexcept; | |
512 | ||
513 | [variablelist | |
514 | [[Effects:] [Moves underlying capture record to `*this`.]] | |
515 | [[Throws:] [Nothing.]] | |
516 | ] | |
517 | ||
518 | [move_assignment_heading ecv2..move assignment] | |
519 | ||
520 | execution_context & operator=( execution_context && other) noexcept; | |
521 | ||
522 | [variablelist | |
523 | [[Effects:] [Moves the state of `other` to `*this` using move semantics.]] | |
524 | [[Throws:] [Nothing.]] | |
525 | ] | |
526 | ||
527 | [operator_heading ecv2..operator_bool..operator bool] | |
528 | ||
529 | explicit operator bool() const noexcept; | |
530 | ||
531 | [variablelist | |
532 | [[Returns:] [`true` if `*this` points to a capture record.]] | |
533 | [[Throws:] [Nothing.]] | |
534 | ] | |
535 | ||
536 | [operator_heading ecv2..operator_not..operator!] | |
537 | ||
538 | bool operator!() const noexcept; | |
539 | ||
540 | [variablelist | |
541 | [[Returns:] [`true` if `*this` does not point to a capture record.]] | |
542 | [[Throws:] [Nothing.]] | |
543 | ] | |
544 | ||
545 | [operator_heading ecv2..operator_call..operator()] | |
546 | ||
547 | std::tuple< execution_context< Args ... >, Args ... > operator()( Args ... args); // member of generic execution_context template | |
548 | ||
549 | execution_context< void > operator()(); // member of execution_context< void > | |
550 | ||
551 | [variablelist | |
552 | [[Effects:] [Stores internally the current context data (stack pointer, | |
553 | instruction pointer, and CPU registers) of the current active context and | |
554 | restores the context data from `*this`, which implies jumping to `*this`'s | |
555 | context. | |
556 | The arguments, `... args`, are passed to the current context to be returned | |
557 | by the most recent call to `execution_context::operator()` in the same thread.]] | |
558 | [[Returns:] [The tuple of execution_context and returned arguments passed to the | |
559 | most recent call to `execution_context::operator()`, if any and a | |
560 | execution_context representing the context that has been suspended.]] | |
561 | [[Note:] [The returned execution_context indicates if the suspended context has | |
562 | terminated (return from context-function) via `bool operator()`. If the returned | |
563 | execution_context has terminated no data are transferred in the returned tuple.]] | |
564 | ] | |
565 | ||
566 | [operator_heading ecv2..operator_call_ontop..operator()] | |
567 | ||
568 | template< typename Fn > | |
569 | std::tuple< execution_context< Args ... >, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); // member of generic execution_context | |
570 | ||
571 | template< typename Fn > | |
572 | execution_context< void > operator()( exec_ontop_arg_t, Fn && fn); // member of execution_context< void > | |
573 | ||
574 | [variablelist | |
575 | [[Effects:] [Same as __ec_op__. Additionally, function `fn` is executed | |
576 | in the context of `*this` (e.g. the stack frame of `fn` is allocated on | |
577 | stack of `*this`).]] | |
578 | [[Returns:] [The tuple of execution_context and returned arguments passed to the | |
579 | most recent call to `execution_context::operator()`, if any and a | |
580 | execution_context representing the context that has been suspended .]] | |
581 | [[Note:] [The tuple of execution_context and returned arguments from `fn` are | |
582 | passed as arguments to the context-function of resumed context (if the context | |
583 | is entered the first time) or those arguments are returned from | |
584 | `execution_context::operator()` within the resumed context.]] | |
585 | [[Note:] [Function `fn` needs to return a tuple of execution_context and | |
586 | arguments ([link ecv2_ontop see description]).]] | |
587 | [[Note:] [The context calling this function must not be destroyed before the | |
588 | arguments, that will be returned from `fn`, are preserved at least in the stack | |
589 | frame of the resumed context.]] | |
590 | [[Note:] [The returned execution_context indicates if the suspended context has | |
591 | terminated (return from context-function) via `bool operator()`. If the returned | |
592 | execution_context has terminated no data are transferred in the returned tuple.]] | |
593 | ] | |
594 | ||
595 | [operator_heading ecv2..operator_equal..operator==] | |
596 | ||
597 | bool operator==( execution_context const& other) const noexcept; | |
598 | ||
599 | [variablelist | |
600 | [[Returns:] [`true` if `*this` and `other` represent the same execution context, | |
601 | `false` otherwise.]] | |
602 | [[Throws:] [Nothing.]] | |
603 | ] | |
604 | ||
605 | [operator_heading ecv2..operator_notequal..operator!=] | |
606 | ||
607 | bool operator!=( execution_context const& other) const noexcept; | |
608 | ||
609 | [variablelist | |
610 | [[Returns:] [[`! (other == * this)]]] | |
611 | [[Throws:] [Nothing.]] | |
612 | ] | |
613 | ||
614 | [operator_heading ecv2..operator_less..operator<] | |
615 | ||
616 | bool operator<( execution_context const& other) const noexcept; | |
617 | ||
618 | [variablelist | |
619 | [[Returns:] [`true` if `*this != other` is true and the | |
620 | implementation-defined total order of `execution_context` values places `*this` | |
621 | before `other`, false otherwise.]] | |
622 | [[Throws:] [Nothing.]] | |
623 | ] | |
624 | ||
625 | [operator_heading ecv2..operator_greater..operator>] | |
626 | ||
627 | bool operator>( execution_context const& other) const noexcept; | |
628 | ||
629 | [variablelist | |
630 | [[Returns:] [`other < * this`]] | |
631 | [[Throws:] [Nothing.]] | |
632 | ] | |
633 | ||
634 | [operator_heading ecv2..operator_lesseq..operator<=] | |
635 | ||
636 | bool operator<=( execution_context const& other) const noexcept; | |
637 | ||
638 | [variablelist | |
639 | [[Returns:] [`! (other < * this)`]] | |
640 | [[Throws:] [Nothing.]] | |
641 | ] | |
642 | ||
643 | [operator_heading ecv2..operator_greatereq..operator>=] | |
644 | ||
645 | bool operator>=( execution_context const& other) const noexcept; | |
646 | ||
647 | [variablelist | |
648 | [[Returns:] [`! (* this < other)`]] | |
649 | [[Throws:] [Nothing.]] | |
650 | ] | |
651 | ||
652 | [hding ecv2_..Non-member function [`operator<<()]] | |
653 | ||
654 | template< typename charT, class traitsT > | |
655 | std::basic_ostream< charT, traitsT > & | |
656 | operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); | |
657 | ||
658 | [variablelist | |
659 | [[Efects:] [Writes the representation of `other` to stream `os`.]] | |
660 | [[Returns:] [`os`]] | |
661 | ] | |
662 | ||
663 | ||
664 | [endsect] |