Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Asymmetric coroutine

Class asymmetric_coroutine<>::pull_type
Class asymmetric_coroutine<>::push_type

Two asymmetric coroutine types - asymmetric_coroutine<>::push_type and asymmetric_coroutine<>::pull_type - provide a unidirectional transfer of data.

asymmetric_coroutine<>::pull_type

asymmetric_coroutine<>::pull_type transfers data from another execution context (== pulled-from). The template parameter defines the transferred parameter type. The constructor of asymmetric_coroutine<>::pull_type takes a function (coroutine-function) accepting a reference to an asymmetric_coroutine<>::push_type as argument. Instantiating an asymmetric_coroutine<>::pull_type passes the control of execution to coroutine-function and a complementary asymmetric_coroutine<>::push_type is synthesized by the library and passed as reference to coroutine-function.

This kind of coroutine provides asymmetric_coroutine<>::pull_type::operator(). This method only switches context; it transfers no data.

asymmetric_coroutine<>::pull_type provides input iterators (asymmetric_coroutine<>::pull_type::iterator) and std::begin()/std::end() are overloaded. The increment-operation switches the context and transfers data.

boost::coroutines::asymmetric_coroutine<int>::pull_type source(
    [&](boost::coroutines::asymmetric_coroutine<int>::push_type& sink){
        int first=1,second=1;
        sink(first);
        sink(second);
        for(int i=0;i<8;++i){
            int third=first+second;
            first=second;
            second=third;
            sink(third);
        }
    });

for(auto i:source)
    std::cout << i <<  " ";

output:
1 1 2 3 5 8 13 21 34 55

In this example an asymmetric_coroutine<>::pull_type is created in the main execution context taking a lambda function (== coroutine-function) which calculates Fibonacci numbers in a simple for-loop. The coroutine-function is executed in a newly created execution context which is managed by the instance of asymmetric_coroutine<>::pull_type. An asymmetric_coroutine<>::push_type is automatically generated by the library and passed as reference to the lambda function. Each time the lambda function calls asymmetric_coroutine<>::push_type::operator() with another Fibonacci number, asymmetric_coroutine<>::push_type transfers it back to the main execution context. The local state of coroutine-function is preserved and will be restored upon transferring execution control back to coroutine-function to calculate the next Fibonacci number. Because asymmetric_coroutine<>::pull_type provides input iterators and std::begin()/std::end() are overloaded, a range-based for-loop can be used to iterate over the generated Fibonacci numbers.

asymmetric_coroutine<>::push_type

asymmetric_coroutine<>::push_type transfers data to the other execution context (== pushed-to). The template parameter defines the transferred parameter type. The constructor of asymmetric_coroutine<>::push_type takes a function (coroutine-function) accepting a reference to an asymmetric_coroutine<>::pull_type as argument. In contrast to asymmetric_coroutine<>::pull_type, instantiating an asymmetric_coroutine<>::push_type does not pass the control of execution to coroutine-function - instead the first call of asymmetric_coroutine<>::push_type::operator() synthesizes a complementary asymmetric_coroutine<>::pull_type and passes it as reference to coroutine-function.

The asymmetric_coroutine<>::push_type interface does not contain a get()-function: you can not retrieve values from another execution context with this kind of coroutine.

asymmetric_coroutine<>::push_type provides output iterators (asymmetric_coroutine<>::push_type::iterator) and std::begin()/std::end() are overloaded. The increment-operation switches the context and transfers data.

struct FinalEOL{
    ~FinalEOL(){
        std::cout << std::endl;
    }
};

const int num=5, width=15;
boost::coroutines::asymmetric_coroutine<std::string>::push_type writer(
    [&](boost::coroutines::asymmetric_coroutine<std::string>::pull_type& in){
        // finish the last line when we leave by whatever means
        FinalEOL eol;
        // pull values from upstream, lay them out 'num' to a line
        for (;;){
            for(int i=0;i<num;++i){
                // when we exhaust the input, stop
                if(!in) return;
                std::cout << std::setw(width) << in.get();
                // now that we've handled this item, advance to next
                in();
            }
            // after 'num' items, line break
            std::cout << std::endl;
        }
    });

std::vector<std::string> words{
    "peas", "porridge", "hot", "peas",
    "porridge", "cold", "peas", "porridge",
    "in", "the", "pot", "nine",
    "days", "old" };

std::copy(boost::begin(words),boost::end(words),boost::begin(writer));

output:
           peas       porridge            hot           peas       porridge
           cold           peas       porridge             in            the
            pot           nine           days            old

In this example an asymmetric_coroutine<>::push_type is created in the main execution context accepting a lambda function (== coroutine-function) which requests strings and lays out 'num' of them on each line. This demonstrates the inversion of control permitted by coroutines. Without coroutines, a utility function to perform the same job would necessarily accept each new value as a function parameter, returning after processing that single value. That function would depend on a static state variable. A coroutine-function, however, can request each new value as if by calling a function -- even though its caller also passes values as if by calling a function. The coroutine-function is executed in a newly created execution context which is managed by the instance of asymmetric_coroutine<>::push_type. The main execution context passes the strings to the coroutine-function by calling asymmetric_coroutine<>::push_type::operator(). An asymmetric_coroutine<>::pull_type instance is automatically generated by the library and passed as reference to the lambda function. The coroutine-function accesses the strings passed from the main execution context by calling asymmetric_coroutine<>::pull_type::get() and lays those strings out on std::cout according the parameters 'num' and 'width'. The local state of coroutine-function is preserved and will be restored after transferring execution control back to coroutine-function. Because asymmetric_coroutine<>::push_type provides output iterators and std::begin()/std::end() are overloaded, the std::copy algorithm can be used to iterate over the vector containing the strings and pass them one by one to the coroutine.

coroutine-function

The coroutine-function returns void and takes its counterpart-coroutine as argument, so that using the coroutine passed as argument to coroutine-function is the only way to transfer data and execution control back to the caller. Both coroutine types take the same template argument. For asymmetric_coroutine<>::pull_type the coroutine-function is entered at asymmetric_coroutine<>::pull_type construction. For asymmetric_coroutine<>::push_type the coroutine-function is not entered at asymmetric_coroutine<>::push_type construction but entered by the first invocation of asymmetric_coroutine<>::push_type::operator(). After execution control is returned from coroutine-function the state of the coroutine can be checked via asymmetric_coroutine<>::pull_type::operator bool returning true if the coroutine is still valid (coroutine-function has not terminated). Unless the first template parameter is void, true also implies that a data value is available.

passing data from a pull-coroutine to main-context

In order to transfer data from an asymmetric_coroutine<>::pull_type to the main-context the framework synthesizes an asymmetric_coroutine<>::push_type associated with the asymmetric_coroutine<>::pull_type instance in the main-context. The synthesized asymmetric_coroutine<>::push_type is passed as argument to coroutine-function. The coroutine-function must call this asymmetric_coroutine<>::push_type::operator() in order to transfer each data value back to the main-context. In the main-context, the asymmetric_coroutine<>::pull_type::operator bool determines whether the coroutine is still valid and a data value is available or coroutine-function has terminated (asymmetric_coroutine<>::pull_type is invalid; no data value available). Access to the transferred data value is given by asymmetric_coroutine<>::pull_type::get().

boost::coroutines::asymmetric_coroutine<int>::pull_type source( // constructor enters coroutine-function
    [&](boost::coroutines::asymmetric_coroutine<int>::push_type& sink){
        sink(1); // push {1} back to main-context
        sink(1); // push {1} back to main-context
        sink(2); // push {2} back to main-context
        sink(3); // push {3} back to main-context
        sink(5); // push {5} back to main-context
        sink(8); // push {8} back to main-context
    });

while(source){            // test if pull-coroutine is valid
    int ret=source.get(); // access data value
    source();             // context-switch to coroutine-function
}
passing data from main-context to a push-coroutine

In order to transfer data to an asymmetric_coroutine<>::push_type from the main-context the framework synthesizes an asymmetric_coroutine<>::pull_type associated with the asymmetric_coroutine<>::push_type instance in the main-context. The synthesized asymmetric_coroutine<>::pull_type is passed as argument to coroutine-function. The main-context must call this asymmetric_coroutine<>::push_type::operator() in order to transfer each data value into the coroutine-function. Access to the transferred data value is given by asymmetric_coroutine<>::pull_type::get().

boost::coroutines::asymmetric_coroutine<int>::push_type sink( // constructor does NOT enter coroutine-function
    [&](boost::coroutines::asymmetric_coroutine<int>::pull_type& source){
        for (int i:source) {
            std::cout << i <<  " ";
        }
    });

std::vector<int> v{1,1,2,3,5,8,13,21,34,55};
for( int i:v){
    sink(i); // push {i} to coroutine-function
}
accessing parameters

Parameters returned from or transferred to the coroutine-function can be accessed with asymmetric_coroutine<>::pull_type::get().

Splitting-up the access of parameters from context switch function enables to check if asymmetric_coroutine<>::pull_type is valid after return from asymmetric_coroutine<>::pull_type::operator(), e.g. asymmetric_coroutine<>::pull_type has values and coroutine-function has not terminated.

boost::coroutines::asymmetric_coroutine<boost::tuple<int,int>>::push_type sink(
    [&](boost::coroutines::asymmetric_coroutine<boost::tuple<int,int>>::pull_type& source){
        // access tuple {7,11}; x==7 y==1
        int x,y;
        boost::tie(x,y)=source.get();
    });

sink(boost::make_tuple(7,11));
exceptions

An exception thrown inside an asymmetric_coroutine<>::pull_type's coroutine-function before its first call to asymmetric_coroutine<>::push_type::operator() will be re-thrown by the asymmetric_coroutine<>::pull_type constructor. After an asymmetric_coroutine<>::pull_type's coroutine-function's first call to asymmetric_coroutine<>::push_type::operator(), any subsequent exception inside that coroutine-function will be re-thrown by asymmetric_coroutine<>::pull_type::operator(). asymmetric_coroutine<>::pull_type::get() does not throw.

An exception thrown inside an asymmetric_coroutine<>::push_type's coroutine-function will be re-thrown by asymmetric_coroutine<>::push_type::operator().

[Important] Important

Code executed by coroutine-function must not prevent the propagation of the detail::forced_unwind exception. Absorbing that exception will cause stack unwinding to fail. Thus, any code that catches all exceptions must re-throw any pending detail::forced_unwind exception.

try {
    // code that might throw
} catch(const boost::coroutines::detail::forced_unwind&) {
    throw;
} catch(...) {
    // possibly not re-throw pending exception
}
[Important] Important

Do not jump from inside a catch block and then re-throw the exception in another execution context.

Stack unwinding

Sometimes it is necessary to unwind the stack of an unfinished coroutine to destroy local stack variables so they can release allocated resources (RAII pattern). The attributes argument of the coroutine constructor indicates whether the destructor should unwind the stack (stack is unwound by default).

Stack unwinding assumes the following preconditions:

After unwinding, a coroutine is complete.

struct X {
    X(){
        std::cout<<"X()"<<std::endl;
    }

    ~X(){
        std::cout<<"~X()"<<std::endl;
    }
};

{
    boost::coroutines::asymmetric_coroutine<void>::push_type sink(
        [&](boost::coroutines::asymmetric_coroutine<void>::pull_type& source){
            X x;
            for(int i=0;;++i){
                std::cout<<"fn(): "<<i<<std::endl;
                // transfer execution control back to main()
                source();
            }
        });

    sink();
    sink();
    sink();
    sink();
    sink();

    std::cout<<"sink is complete: "<<std::boolalpha<<!sink<<"\n";
}

output:
    X()
    fn(): 0
    fn(): 1
    fn(): 2
    fn(): 3
    fn(): 4
    sink is complete: false
    ~X()
Range iterators

Boost.Coroutine provides output- and input-iterators using Boost.Range. asymmetric_coroutine<>::pull_type can be used via input-iterators using std::begin() and std::end().

int number=2,exponent=8;
boost::coroutines::asymmetric_coroutine< int >::pull_type source(
    [&]( boost::coroutines::asymmetric_coroutine< int >::push_type & sink){
        int counter=0,result=1;
        while(counter++<exponent){
            result=result*number;
            sink(result);
        }
    });

for (auto i:source)
    std::cout << i <<  " ";

output:
    2 4 8 16 32 64 128 256

asymmetric_coroutine<>::pull_type::iterator::operator++() corresponds to asymmetric_coroutine<>::pull_type::operator(); asymmetric_coroutine<>::pull_type::iterator::operator*() roughly corresponds to asymmetric_coroutine<>::pull_type::get(). An iterator originally obtained from std::begin() of an asymmetric_coroutine<>::pull_type compares equal to an iterator obtained from std::end() of that same asymmetric_coroutine<>::pull_type instance when its asymmetric_coroutine<>::pull_type::operator bool would return false].

[Note] Note

If T is a move-only type, then asymmetric_coroutine<T>::pull_type::iterator may only be dereferenced once before it is incremented again.

Output-iterators can be created from asymmetric_coroutine<>::push_type.

boost::coroutines::asymmetric_coroutine<int>::push_type sink(
    [&](boost::coroutines::asymmetric_coroutine<int>::pull_type& source){
        while(source){
            std::cout << source.get() <<  " ";
            source();
        }
    });

std::vector<int> v{1,1,2,3,5,8,13,21,34,55};
std::copy(boost::begin(v),boost::end(v),boost::begin(sink));

asymmetric_coroutine<>::push_type::iterator::operator*() roughly corresponds to asymmetric_coroutine<>::push_type::operator(). An iterator originally obtained from std::begin() of an asymmetric_coroutine<>::push_type compares equal to an iterator obtained from std::end() of that same asymmetric_coroutine<>::push_type instance when its asymmetric_coroutine<>::push_type::operator bool would return false.

Exit a coroutine-function

coroutine-function is exited with a simple return statement jumping back to the calling routine. The asymmetric_coroutine<>::pull_type, asymmetric_coroutine<>::push_type becomes complete, e.g. asymmetric_coroutine<>::pull_type::operator bool, asymmetric_coroutine<>::push_type::operator bool will return false.

[Important] Important

After returning from coroutine-function the coroutine is complete (can not resumed with asymmetric_coroutine<>::push_type::operator(), asymmetric_coroutine<>::pull_type::operator()).


PrevUpHomeNext