|  | Home | Libraries | People | FAQ | More | 
| ![[Note]](../../../../../doc/src/images/note.png) | Note | 
|---|---|
| This class is enabled per default. | 
Class execution_context encapsulates context switching and manages the associated context' stack (allocation/deallocation).
      execution_context allocates the context stack (using its
      StackAllocator argument)
      and creates a control structure on top of it. This structure is responsible
      for managing context' stack. The address of the control structure is stored
      in the first frame of context' stack (e.g. it can not directly accessed from
      within execution_context). In contrast to execution_context
      (v1) the ownership of the control structure is not shared (no member
      variable to control structure in execution_context).
      execution_context keeps internally a state that is moved
      by a call of execution_context::operator() (*this will be
      invalidated), e.g. after a calling execution_context::operator(),
      *this
      can not be used for an additional context switch.
    
execution_context is only move-constructible and move-assignable.
The moved state is assigned to a new instance of execution_context. This object becomes the first argument of the context-function, if the context was resumed the first time, or the first element in a tuple returned by execution_context::operator() that has been called in the resumed context. In contrast to execution_context (v1), the context switch is faster because no global pointer etc. is involved.
| ![[Important]](../../../../../doc/src/images/important.png) | Important | 
|---|---|
| Segmented stacks are not supported by execution_context (v2). | 
On return the context-function of the current context has to specify an execution_context to which the execution control is transferred after termination of the current context.
If an instance with valid state goes out of scope and the context-function has not yet returned, the stack is traversed in order to access the control structure (address stored at the first stack frame) and context' stack is deallocated via the StackAllocator. The stack walking makes the destruction of execution_context slow and should be prevented if possible.
      execution_context expects a context-function
      with signature execution_context(execution_context
      ctx, Args ... args). The
      parameter ctx represents the
      context from which this context was resumed (e.g. that has called execution_context::operator()
      on *this)
      and args are the data passed
      to execution_context::operator(). The return value represents
      the execution_context that has to be resumed, after termiantion of this context.
    
Benefits of execution_context (v2) over execution_context (v1) are: faster context switch, type-safety of passed/returned arguments.
int n=35; ctx::execution_context<int> source( [n](ctx::execution_context<int> sink, int) mutable { int a=0; int b=1; while(n-->0){ auto result=sink(a); sink=std::move(std::get<0>(result)); auto next=a+b; a=b; b=next; } return sink; }); for(int i=0;i<10;++i){ auto result=source(i); source=std::move(std::get<0>(result)); std::cout<<std::get<1>(result)<<" "; } output: 0 1 1 2 3 5 8 13 21 34
      This simple example demonstrates the basic usage of execution_context
      as a generator. The context sink
      represents the main-context (function main()
      running). sink is generated
      by the framework (first element of lambda's parameter list). Because the state
      is invalidated (== changed) by each call of execution_context::operator(),
      the new state of the execution_context, returned by execution_context::operator(),
      needs to be assigned to sink
      after each call.
    
      The lambda that calculates the Fibonacci numbers is executed inside the context
      represented by source. Calculated
      Fibonacci numbers are transferred between the two context' via expression
      sink(a) (and returned by source()).
      Note that this example represents a generator thus the
      value transferred into the lambda via source() is not
      used. Using boost::optional<> as transferred type,
      might also appropriate to express this fact.
    
      The locale variables a, b and  next
      remain their values during each context switch (yield(a)).
      This is possible due source
      has its own stack and the stack is exchanged by each context switch.
    
      With execution_context<void> no
      data will be transferred, only the context switch is executed.
    
boost::context::execution_context<void> ctx1([](boost::context::execution_context<void> ctx2){ std::printf("inside ctx1\n"); return ctx2(); }); ctx1(); output: inside ctx1
      ctx1()
      resumes ctx1, e.g. the lambda
      passed at the constructor of ctx1
      is entered. Argument ctx2 represents
      the context that has been suspended with the invocation of ctx1(). When the lambda returns ctx2,
      context ctx1 will be terminated
      while the context represented by ctx2
      is resumed, hence the control of execution returns from ctx1().
    
The arguments passed to execution_context::operator(), in one context, is passed as the last arguments of the context-function if the context is started for the first time. In all following invocations of execution_context::operator() the arguments passed to execution_context::operator(), in one context, is returned by execution_context::operator() in the other context.
boost::context::execution_context<int> ctx1([](boost::context::execution_context<int> ctx2, int j){ std::printf("inside ctx1, j == %d\n", j); return ctx2(j+1); }); int i = 1; std::tie(ctx1, i) = ctx1(i); std::printf("i == %d\n", i); output: inside ctx1, j == 1 i == 2
      ctx1(i) enters
      the lambda in context ctx1
      with argument j=1. The expression ctx2(j+1) resumes the
      context represented by ctx2
      and transfers back an integer of j+1. On return
      of ctx1(i), the variable
      i contains the value of j+1.
    
If more than one argument has to be transferred, the signature of the context-function is simply extended.
boost::context::execution_context<int,int> ctx1([](boost::context::execution_context<int,int> ctx2, int i, int j){ std::printf("inside ctx1, i == %d j == %d\n", i, j); return ctx2(i+j,i-j); }); int i = 2, j = 1; std::tie(ctx1, i, j) = ctx1(i,j); std::printf("i == %d j == %d\n", i, j); output: inside ctx1, i == 2 j == 1 i == 3 j == 1
For use-cases, that require to transfer data of different type in each direction, boost::variant<> could be used.
class X{ private: std::exception_ptr excptr_; boost::context::execution_context<boost::variant<int,std::string>> ctx_; public: X(): excptr_(), ctx_( [=](boost::context::execution_context<boost::variant<int,std::string>> ctx, boost::variant<int,std::string> data){ try { for (;;) { int i = boost::get<int>(data); data = boost::lexical_cast<std::string>(i); auto result = ctx( data); ctx = std::move( std::get<0>( result) ); data = std::get<1>( result); } catch (std::bad_cast const&) { excptr_=std::current_exception(); } return ctx; }) {} std::string operator()(int i){ boost::variant<int,std::string> data = i; auto result = ctx_( data); ctx_ = std::move( std::get<0>( result) ); data = std::get<1>( result); if(excptr_){ std::rethrow_exception(excptr_); } return boost::get<std::string>(data); } }; X x; std::cout << x( 7) << std::endl; output: 7
In the case of unidirectional transfer of data, boost::optional<> or a pointer are appropriate.
If the function executed inside a execution_context emits ans exception, the application is terminated by calling std::terminate(). std::exception_ptr can be used to transfer exceptions between different execution contexts.
| ![[Important]](../../../../../doc/src/images/important.png) | Important | 
|---|---|
| Do not jump from inside a catch block and then re-throw the exception in another execution context. | 
      Sometimes it is useful to execute a new function on top of a resumed context.
      For this purpose execution_context::operator() with first
      argument exec_ontop_arg has
      to be used. The function passed as argument must return a tuple of execution_context
      and arguments.
    
boost::context::execution_context<int> f1(boost::context::execution_context<int> ctx,int data) { std::cout << "f1: entered first time: " << data << std::endl; std::tie(ctx,data) = ctx(data+1); std::cout << "f1: entered second time: " << data << std::endl; std::tie(ctx,data) = ctx(data+1); std::cout << "f1: entered third time: " << data << std::endl; return ctx; } std::tuple<boost::context::execution_context<int>,int> f2(boost::context::execution_context<int> ctx,int data) { std::cout << "f2: entered: " << data << std::endl; return std::make_tuple(std::move(ctx),-1); } int data = 0; ctx::execution_context< int > ctx(f1); std::tie(ctx,data) = ctx(data+1); std::cout << "f1: returned first time: " << data << std::endl; std::tie(ctx,data) = ctx(data+1); std::cout << "f1: returned second time: " << data << std::endl; std::tie(ctx,data) = ctx(ctx::exec_ontop_arg,f2,data+1); output: f1: entered first time: 1 f1: returned first time: 2 f1: entered second time: 3 f1: returned second time: 4 f2: entered: 5 f1: entered third time: -1
      The expression ctx(ctx::exec_ontop_arg,f2,data+1) executes f2() on top of context ctx,
      e.g. an additional stack frame is allocated on top of the context stack (in
      front of f1()).
      f2()
      returns argument -1
      that will returned by the second invocation of ctx(data+1) in f1().
    
Another option is to execute a function on top of the context that throws an exception.
struct interrupt { boost::context::execution_context< void > ctx; interrupt( boost::context::execution_context< void > && ctx_) : ctx( std::forward< boost::context::execution_context< void > >( ctx_) ) { } }; boost::context::execution_context<void> f1(boost::context::execution_context<void> ctx) { try { for (;;) { std::cout << "f1()" << std::endl; ctx = ctx(); } } catch (interrupt & e) { std::cout << "f1(): interrupted" << std::endl; ctx = std::move( e.ctx); } return ctx; } boost::context::execution_context<void> f2(boost::context::execution_context<void> ctx) { throw interrupt(std::move(ctx)); return ctx; } boost::context::execution_context< void > ctx(f1); ctx = ctx(); ctx = ctx(); ctx = ctx(boost::context::exec_ontop_arg,f2); output: f1() f1() f1(): interrupted
      In this example f2()
      is used to interrupt the for-loop
      in f1().
    
      On construction of execution_context a stack is allocated.
      If the context-function returns the stack will be destructed.
      If the context-function has not yet returned and the destructor
      of an valid execution_context instance (e.g. execution_context::operator
      bool() returns true)
      is called, the stack will be destructed too.
    
Allocating control structures on top of the stack requires to allocated the stack_context and create the control structure with placement new before execution_context is created.
| ![[Note]](../../../../../doc/src/images/note.png) | Note | 
|---|---|
| The user is responsible for destructing the control structure at the top of the stack. | 
// stack-allocator used for (de-)allocating stack fixedsize_stack salloc( 4048); // allocate stack space stack_context sctx( salloc.allocate() ); // reserve space for control structure on top of the stack void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure); std::size_t size = sctx.size - sizeof( my_control_structure); // placement new creates control structure on reserved space my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc); ... // destructing the control structure cs->~my_control_structure(); ... struct my_control_structure { // captured context execution_context cctx; template< typename StackAllocator > my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) : // create captured context cctx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) { } ... };
/* * grammar: * P ---> E '\0' * E ---> T {('+'|'-') T} * T ---> S {('*'|'/') S} * S ---> digit | '(' E ')' */ class Parser{ // implementation omitted; see examples directory }; std::istringstream is("1+1"); bool done=false; std::exception_ptr except; // execute parser in new execution context boost::context::execution_context<char> source( [&is,&done,&except](ctx::execution_context<char> sink,char){ // create parser with callback function Parser p( is, [&sink](char ch){ // resume main execution context auto result = sink(ch); sink = std::move(std::get<0>(result)); }); try { // start recursive parsing p.run(); } catch (...) { // store other exceptions in exception-pointer except = std::current_exception(); } // set termination flag done=true; // resume main execution context return sink; }); // user-code pulls parsed data from parser // invert control flow auto result = source('\0'); source = std::move(std::get<0>(result)); char c = std::get<1>(result); if ( except) { std::rethrow_exception(except); } while( ! done) { printf("Parsed: %c\n",c); std::tie(source,c) = source('\0'); if (except) { std::rethrow_exception(except); } } output: Parsed: 1 Parsed: + Parsed: 1
In this example a recursive descent parser uses a callback to emit a newly passed symbol. Using execution_context the control flow can be inverted, e.g. the user-code pulls parsed symbols from the parser - instead to get pushed from the parser (via callback).
The data (character) is transferred between the two execution_context.
If the code executed by execution_context emits an exception, the application is terminated. std::exception_ptr can be used to transfer exceptions between different execution contexts.
Sometimes it is necessary to unwind the stack of an unfinished context to destroy local stack variables so they can release allocated resources (RAII pattern). The user is responsible for this task.
execution_context
    struct exec_ontop_arg_t {}; const exec_ontop_arg_t exec_ontop_arg{}; template< typename ... Args > class execution_context { public: template< typename Fn, typename ... Params > execution_context( Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params); template< typename Fn, typename ... Params > execution_context( std::allocator_arg_t, segemented_stack, Fn && fn, Params && ... params) = delete; template< typename Fn, typename ... Params > execution_context( std::allocator_arg_t, preallocated palloc, segmented, Fn && fn, Params && ... params)= delete; ~execution_context(); execution_context( execution_context && other) noexcept; execution_context & operator=( execution_context && other) noexcept; execution_context( execution_context const& other) noexcept = delete; execution_context & operator=( execution_context const& other) noexcept = delete; explicit operator bool() const noexcept; bool operator!() const noexcept; std::tuple< execution_context, Args ... > operator()( Args ... args); template< typename Fn > std::tuple< execution_context, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); bool operator==( execution_context const& other) const noexcept; bool operator!=( execution_context const& other) const noexcept; bool operator<( execution_context const& other) const noexcept; bool operator>( execution_context const& other) const noexcept; bool operator<=( execution_context const& other) const noexcept; bool operator>=( execution_context const& other) const noexcept; template< typename charT, class traitsT > friend std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); };
template< typename Fn, typename ... Params > execution_context( Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params);
            Creates a new execution context and prepares the context to execute
            fn. fixedsize_stack
            is used as default stack allocator (stack size == fixedsize_stack::traits::default_size()).
            The constructor with argument type preallocated,
            is used to create a user defined data (for
            instance additional control structures) on top of the stack.
          
~execution_context();
            Destructs the associated stack if *this is a valid context, e.g. execution_context::operator
            bool() returns true.
          
Nothing.
execution_context( execution_context && other) noexcept;
            Moves underlying capture record to *this.
          
Nothing.
execution_context & operator=( execution_context && other) noexcept;
            Moves the state of other
            to *this
            using move semantics.
          
Nothing.
operator bool()
explicit operator bool() const noexcept;
            true if *this points to a capture record.
          
Nothing.
operator!()
bool operator!() const noexcept;
            true if *this does not point to a capture record.
          
Nothing.
operator()()
std::tuple< execution_context< Args ... >, Args ... > operator()( Args ... args); // member of generic execution_context template execution_context< void > operator()(); // member of execution_context< void >
            Stores internally the current context data (stack pointer, instruction
            pointer, and CPU registers) of the current active context and restores
            the context data from *this, which implies jumping to *this's
            context. The arguments, ... args, are passed to the current context
            to be returned by the most recent call to execution_context::operator() in the same thread.
          
            The tuple of execution_context and returned arguments passed to the most
            recent call to execution_context::operator(), if any and a execution_context representing
            the context that has been suspended.
          
            The returned execution_context indicates if the suspended context has
            terminated (return from context-function) via bool
            operator().
            If the returned execution_context has terminated no data are transferred
            in the returned tuple.
          
operator()()
template< typename Fn > std::tuple< execution_context< Args ... >, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); // member of generic execution_context template< typename Fn > execution_context< void > operator()( exec_ontop_arg_t, Fn && fn); // member of execution_context< void >
            Same as execution_context::operator(). Additionally,
            function fn is executed
            in the context of *this
            (e.g. the stack frame of fn
            is allocated on stack of *this).
          
            The tuple of execution_context and returned arguments passed to the most
            recent call to execution_context::operator(), if any and a execution_context representing
            the context that has been suspended .
          
            The tuple of execution_context and returned arguments from fn are passed as arguments to the context-function
            of resumed context (if the context is entered the first time) or those
            arguments are returned from execution_context::operator() within the resumed context.
          
            Function fn needs to
            return a tuple of execution_context and arguments (see
            description).
          
            The context calling this function must not be destroyed before the arguments,
            that will be returned from fn,
            are preserved at least in the stack frame of the resumed context.
          
            The returned execution_context indicates if the suspended context has
            terminated (return from context-function) via bool
            operator().
            If the returned execution_context has terminated no data are transferred
            in the returned tuple.
          
operator==()
bool operator==( execution_context const& other) const noexcept;
            true if *this and other
            represent the same execution context, false
            otherwise.
          
Nothing.
operator!=()
bool operator!=( execution_context const& other) const noexcept;
            ! (other == * this)
          
Nothing.
operator<()
bool operator<( execution_context const& other) const noexcept;
            true if *this != other is true and the implementation-defined
            total order of execution_context
            values places *this
            before other, false otherwise.
          
Nothing.
operator>()
bool operator>( execution_context const& other) const noexcept;
            other <
            * this
          
Nothing.
operator<=()
bool operator<=( execution_context const& other) const noexcept;
            ! (other <
            * this)
          
Nothing.
operator>=()
bool operator>=( execution_context const& other) const noexcept;
            ! (*
            this <
            other)
          
Nothing.
operator<<()
template< typename charT, class traitsT > std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
            Writes the representation of other
            to stream os.
          
            os