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

This is the documentation for an old version of Boost. Click here to view this page for the latest version.
PrevUpHomeNext

Asymmetric coroutine

Class coroutine<>::pull_type
Class coroutine<>::push_type

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

[Note] Note

asymmetric_coroutine<> is a typedef of coroutine.

coroutine<>::pull_type

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

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

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

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::pull_type source(
    [&](coro_t::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 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 coroutine<>::pull_type. An coroutine<>::push_type is automatically generated by the library and passed as reference to the lambda function. Each time the lambda function calls coroutine<>::push_type::operator() with another Fibonacci number, 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 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.

coroutine<>::push_type

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

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

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

typedef boost::coroutines2::coroutine<std::string>   coro_t;

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

const int num=5, width=15;
coro_t::push_type writer(
    [&](coro_t::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(begin(words),end(words),begin(writer));

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

In this example an 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 coroutine<>::push_type. The main execution context passes the strings to the coroutine-function by calling coroutine<>::push_type::operator(). An 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 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 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 coroutine<>::pull_type the coroutine-function is entered at coroutine<>::pull_type construction. For coroutine<>::push_type the coroutine-function is not entered at coroutine<>::push_type construction but entered by the first invocation of coroutine<>::push_type::operator(). After execution control is returned from coroutine-function the state of the coroutine can be checked via 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 coroutine<>::pull_type to the main-context the framework synthesizes an coroutine<>::push_type associated with the coroutine<>::pull_type instance in the main-context. The synthesized coroutine<>::push_type is passed as argument to coroutine-function. The coroutine-function must call this coroutine<>::push_type::operator() in order to transfer each data value back to the main-context. In the main-context, the coroutine<>::pull_type::operator bool determines whether the coroutine is still valid and a data value is available or coroutine-function has terminated (coroutine<>::pull_type is invalid; no data value available). Access to the transferred data value is given by coroutine<>::pull_type::get().

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::pull_type source( // constructor enters coroutine-function
    [&](coro_t::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 coroutine<>::push_type from the main-context the framework synthesizes an coroutine<>::pull_type associated with the coroutine<>::push_type instance in the main-context. The synthesized coroutine<>::pull_type is passed as argument to coroutine-function. The main-context must call this coroutine<>::push_type::operator() in order to transfer each data value into the coroutine-function. Access to the transferred data value is given by coroutine<>::pull_type::get().

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::push_type sink( // constructor does NOT enter coroutine-function
    [&](coro_t::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 coroutine<>::pull_type::get().

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

typedef boost::coroutines2::coroutine<boost::tuple<int,int>> coro_t;

coro_t::push_type sink(
    [&](coro_t::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 coroutine<>::pull_type's coroutine-function before its first call to coroutine<>::push_type::operator() will be re-thrown by the coroutine<>::pull_type constructor. After an coroutine<>::pull_type's coroutine-function's first call to coroutine<>::push_type::operator(), any subsequent exception inside that coroutine-function will be re-thrown by coroutine<>::pull_type::operator(). coroutine<>::pull_type::get() does not throw.

An exception thrown inside an coroutine<>::push_type's coroutine-function will be re-thrown by 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::coroutines2::detail::forced_unwind&) {
    throw;
} catch(...) {
    // possibly not re-throw pending exception
}
[Important] Important

Do not jump from inside a catch block and than 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;
    }
};

{
    typedef boost::coroutines2::coroutine<void>::push_type   coro_t;

    coro_t::push_type sink(
        [&](coro_t::pull_type& source){
            X x;
            for(int=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
    fn(): 5
    sink is complete: false
    ~X()
Range iterators

Boost.Coroutine2 provides output- and input-iterators using __boost_range__. coroutine<>::pull_type can be used via input-iterators using std::begin() and std::end().

typedef boost::coroutines2::coroutine< int > coro_t;

int number=2,exponent=8;
coro_t::pull_type source(
    [&](coro_t::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

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

[Note] Note

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

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

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::push_type sink(
    [&](coro_t::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(begin(v),end(v),begin(sink));

coroutine<>::push_type::iterator::operator*() roughly corresponds to coroutine<>::push_type::operator(). An iterator originally obtained from std::begin() of an coroutine<>::push_type compares equal to an iterator obtained from std::end() of that same coroutine<>::push_type instance when its 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 coroutine<>::pull_type, coroutine<>::push_type becomes complete, e.g. coroutine<>::pull_type::operator bool, coroutine<>::push_type::operator bool will return false.

[Important] Important

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


PrevUpHomeNext