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