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

Click here to view the latest version of this page.
PrevUpHomeNext

Coroutine

Class coroutine

Each instance of coroutine has its own context of execution (CPU registers and stack space) or represents not-a-coroutine (similar to boost::thread). Objects of type coroutine are moveable but not copyable and can be returned by a function.

boost::coroutines::coroutine< void() > f();

void g()
{
    boost::coroutines::coroutine< void() > c( f() );
    c();
}
[Note] Note

Boost.Move is used to emulate rvalue references.

Creating a coroutine

A new coroutine is created from a coroutine-function (function or functor) which will be executed in a new context (CPU registers and stack space).

[Note] Note

coroutine-function is required to return void and accept a reference of type boost::coroutines::coroutine<>::caller_type.

The template argument Signature determines the data-types transferred to coroutine-function and from coroutine-function by calling boost::coroutines::coroutine<>::operator() and boost::coroutines::coroutine<>::get().

typedef boost::coroutines::coroutine< int( std::string const&) > coro_t;

// void f( boost::coroutine< std::string const&( int) > & ca)
void f( coro_t::caller_type & ca)
{
    ...
    // access argument
    std::string str( ca.get() );
    ...
    ca( 7);
    ...
}

std::string str;
...
coro_t c( f);
// pass argument
c( str);
// returned value
int res = c.get();

The coroutine-function is started at coroutine construction (similar to boost::thread) in a newly created coroutine complete with registers, flags, stack and instruction pointer. If coroutine-function requires some arguments (types defined by Signature) on start-up those parameters must be applied to the coroutine constructor. A single arguments can be passed as it is:

typedef boost::coroutines::coroutine< int( std::string const&) > coro_t;

// void f( boost::coroutine< std::string const&( int) > & ca)
void f( coro_t::caller_type & ca);

std::string str("abc");
coro_t c( f, str);

For multiple arguments boost::coroutines::coroutine<>::arguments must be used (it is a typedef of boost::tuple<>):

typedef boost::coroutines::coroutine< int( int, std::string const&) > coro_t;

// void f( boost::coroutine< boost::tuple< int, std::string const& >( int) > & ca)
void f( coro_t::caller_type & ca);

std::string str("abc");
coro_t c( f, coro_t::arguments( 7, str) );
[Note] Note

The maximum number of arguments is limited to 10 (limit defined by Boost.Tuple).

[Note] Note

Parameters bound with boost::bind() to coroutine-function will not be part of the boost::coroutines::coroutine<>::operator() signature.

boost::coroutines::attributes, an additional constructor argument of coroutine, defines the stack size, stack unwinding and floating-point preserving behaviour used for context construction.

The coroutine constructor uses the stack-allocator concept to allocate an associated stack, and the destructor uses the same stack-allocator concept to deallocate the stack. The default stack-allocator concept is stack-allocator, but a custom stack-allocator can be passed to the constructor.

Calling a coroutine

The execution control is transferred to coroutine at construction (coroutine-function entered) - when control should be returned to the original calling routine, invoke boost::coroutines::coroutine<>::operator() on the first argument of type boost::coroutines::coroutine<>::caller_type inside coroutine-function. boost::coroutines::coroutine<>::caller_type is a typedef of coroutine with an inverted Signature. Inverted Signature means that the return type becomes an argument and vice versa. Multiple arguments are wrapped into boost::tuple<>.

void f( boost::coroutines::coroutine< std::string const&( int) & ca);
boost::coroutines::coroutine< int( std::string const&) > c1( f);

void g( boost::coroutines::coroutine< boost::tuple< X, Y >( int) & ca);
boost::coroutines::coroutine< int( X, X) > c2( g);

The current coroutine information (registers, flags, and stack and instruction pointer) is saved and the original context information is restored. Calling boost::coroutines::coroutine<>::operator() resumes execution in the coroutine after saving the new state of the original routine.

typedef boost::coroutines::coroutine< void() > coro_t;

// void fn( boost::coroutines::coroutine< void() > & ca, int j)
void fn( coro_t::caller_type & ca, int j)
{
    for( int i = 0; i < j; ++i)
    {
        std::cout << "fn(): local variable i == " << i << std::endl;

        // save current coroutine
        // value of local variable is preserved
        // transfer execution control back to main()
        ca();

        // coroutine<>::operator()() was called
        // execution control transferred back from main()
    }
}

int main( int argc, char * argv[])
{
    // bind parameter '7' to coroutine-fn
    coro_t c( boost::bind( fn, _1, 7) );

    std::cout << "main() starts coroutine c" << std::endl;

    while ( c)
    {
        std::cout << "main() calls coroutine c" << std::endl;
        // execution control is transferred to c
        c();
    }

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}

output:
    main() starts coroutine c
    fn(): local variable i == 0
    main() calls coroutine c
    fn(): local variable i == 1
    main() calls coroutine c
    fn(): local variable i == 2
    main() calls coroutine c
    fn(): local variable i == 3
    main() calls coroutine c
    fn(): local variable i == 4
    main() calls coroutine c
    fn(): local variable i == 5
    main() calls coroutine c
    fn(): local variable i == 6
    main() calls coroutine c
    Done
[Warning] Warning

Calling boost::coroutines::coroutine<>::operator() from inside the same coroutine results in undefined behaviour.

Transfer of data

Signature, the template argument of coroutine, defines the types transferred to and returned from the coroutine-function, e.g. it determines the signature of boost::coroutines::coroutine<>::operator() and the return-type of boost::coroutines::coroutine<>::get().

[Note] Note

boost::coroutines::coroutine<>::caller_type is not part of Signature and coroutine-function is required to return void and accept boost::coroutines::coroutine<>::caller_type as argument.

boost::coroutines::coroutine<>::operator() accepts arguments as defined in Signature and returns a reference to coroutine. The arguments passed to boost::coroutines::coroutine<>::operator(), in one coroutine, is returned (as a boost::tuple<>) by boost::coroutines::coroutine<>::get() in the other coroutine. If coroutine is constructed and arguments are passed to the constructor, the coroutine-function will be entered and the arguments are accessed thorough boost::coroutines::coroutine<>::get() in coroutine-function on entry.

The value given to boost::coroutines::coroutine<>::operator() of boost::coroutines::coroutine<>::caller_type, in one coroutine, is returned by boost::coroutines::coroutine<>::get() in the other routine.

typedef boost::coroutines::coroutine< int( int) >    coro_t;

// void fn( boost::coroutines::coroutine< int( int) > & ca)
void fn( coro_t::caller_type & ca)
{
    // access the integer argument given to coroutine ctor
    int i = ca.get();
    std::cout << "fn(): local variable i == " << i << std::endl;

    // save current coroutine context and
    // transfer execution control back to caller
    // pass content of variable 'i' to caller
    // after execution control is returned back coroutine<>::operator()
    // returns and the transferred integer s accessed via coroutine<>::get()
    i = ca( i).get();

    // i == 10 because c( 10) in main()
    std::cout << "fn(): local variable i == " << i << std::endl;
    ca( i);
}

int main( int argc, char * argv[])
{
    std::cout << "main(): call coroutine c" << std::endl;
    coro_t c( fn, 7);

    int x = c.get();
    std::cout << "main(): transferred value: " << x << std::endl;

    x = c( 10).get();
    std::cout << "main(): transferred value: " << x << std::endl;

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}

output:
    main(): call coroutine c
    fn(): local variable i == 7
    main(): transferred value: 7
    fn(): local variable i == 10
    main(): transferred value: 10
    Done

coroutine-function with multiple arguments

If coroutine-function has more than one argument boost::coroutines::coroutine<>::operator() has the same size of arguments and boost::coroutines::coroutine<>::get() from boost::coroutines::coroutine<>::caller_type returns a boost::tuple<> corresponding to the arguments of Signature. boost::tie helps to access the values stored in the boost::tuple<> returned by boost::coroutines::coroutine<>::get().

typedef boost::coroutines::coroutine< int(int,int) > coro_t;

// void fn( boost::coroutines::coroutine< boost::tuple< int, int >( int) > & ca)
void fn( coro_t::caller_type & ca)
{
    int a, b;
    boost::tie( a, b) = ca.get();
    boost::tie( a, b) = ca( a + b).get();
    ca( a + b);
}

int main( int argc, char * argv[])
{
    std::cout << "main(): call coroutine c" << std::endl;
    coro_t coro( fn, coro_t::arguments( 3, 7) );

    int res = coro.get();
    std::cout << "main(): 3 + 7 == " << res << std::endl;

    res = coro( 5, 7).get();
    std::cout << "main(): 5 + 7 == " << res << std::endl;

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}

output:
    main(): call coroutine c
    main(): 3 + 7 == 10
    main(): 5 + 7 == 12
    Done

Transfer of pointers and references

You can transfer references and pointers from and to coroutines but as usual you must take care (scope, no re-assignment of const references etc.). In the following code x points to local which is allocated on stack of c. When c goes out of scope the stack becomes deallocated. Using x after c is gone will fail!

struct X
{
    void g();
};

typedef boost::coroutines::coroutine< X*() >    coro_t;

// void fn( boost::coroutines::coroutine< void( X*) > & ca)
void fn( coro_t::caller_t & ca) {
    X local;
    ca( & local);
}

int main() {
    X * x = 0;
    {
        coro_t c( fn);
        x = c.get(); // let x point to X on stack owned by c
        // stack gets unwound -> X will be destructed
    }
    x->g(); // segmentation fault!
    return EXIT_SUCCESS;
}

Range iterators

Boost.Coroutine provides output- and input-iterators using Boost.Range. coroutine< T() > can be used via output-iterators using boost::begin() and boost::end().

typedef boost::coroutines::coroutine< int() >       coro_t;
typedef boost::range_iterator< coro_t >::type       iterator_t;

// void power( boost::coroutines::coroutine< void( int) > & ca, int number, int exponent)
void power( coro_t::caller_type & ca, int number, int exponent)
{
    int counter = 0;
    int result = 1;
    while ( counter++ < exponent)
    {
            result = result * number;
            ca( result);
    }
}

int main()
{
    coro_t c( boost::bind( power, _1, 2, 8) );
    iterator_t e( boost::end( c) );
    for ( iterator_t i( boost::begin( c) ); i != e; ++i)
        std::cout << * i <<  " ";

    std::cout << "\nDone" << std::endl;

    return EXIT_SUCCESS;
}

output:
    2 4 8 16 32 64 128 256
    Done

BOOST_FOREACH can be used to iterate over the coroutine range too.

typedef boost::coroutines::coroutine< int() >       coro_t;
typedef boost::range_iterator< coro_t >::type       iterator_t;

// void power( boost::coroutines::coroutine< void( int) > & ca, int number, int exponent)
void power( coro_t::caller_type & ca, int number, int exponent)
{
    int counter = 0;
    int result = 1;
    while ( counter++ < exponent)
    {
            result = result * number;
            ca( result);
    }
}

int main()
{
    coro_t c( boost::bind( power, _1, 2, 8) );
    BOOST_FOREACH( int i, c)
    { std::cout << i <<  " "; }

    std::cout << "\nDone" << std::endl;

    return EXIT_SUCCESS;
}

output:
    2 4 8 16 32 64 128 256
    Done

Input iterators are created from coroutines of type coroutine< void( T) >.

Exit a coroutine-function

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

typedef boost::coroutines::coroutine< int(int,int) > coro_t;

// void power( boost::coroutines::coroutine< boost::tuple< int, int >( int) > & ca, int number, int exponent)
void fn( coro_t::caller_type & ca)
{
    int a, b;
    boost::tie( a, b) = ca.get();
    boost::tie( a, b) = ca( a + b).get();
    ca( a + b);
}

int main( int argc, char * argv[])
{
    std::cout << "main(): call coroutine c" << std::endl;
    coro_t coro( fn, coro_t::arguments( 3, 7) );

    BOOST_ASSERT( coro);
    int res = coro.get();
    std::cout << "main(): 3 + 7 == " << res << std::endl;

    res = coro( 5, 7).get();
    BOOST_ASSERT( ! coro);
    std::cout << "main(): 5 + 7 == " << res << std::endl;

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}

output:
    main(): call coroutine c
    main(): 3 + 7 == 10
    main(): 5 + 7 == 12
    Done
[Important] Important

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

Exceptions in coroutine-function

An exception thrown inside coroutine-function will transferred via exception-pointer (see Boost.Exception for details) and re-thrown by constructor or boost::coroutines::coroutine<>::operator().

typedef boost::coroutines::coroutine< void() >    coro_t;

// void fn( boost::coroutines::coroutine< void() > & ca)
void fn( coro_t::caller_type & ca)
{
    ca();
    throw std::runtime_error("abc");
}

int main( int argc, char * argv[])
{
    coro_t c( f);
    try
    {
        c();
    }
    catch ( std::exception const& e)
    {
        std::cout << "exception catched:" << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}

output:
    exception catched: abc
[Important] Important

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

try
{
    // code that might throw
}
catch( forced_unwind)
{
    throw;
}
catch(...)
{
    // possibly not re-throw pending exception
}

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 third argument of the coroutine constructor, do_unwind, 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.

typedef boost::coroutines::coroutine< void() >    coro_t;

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

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

// void fn( boost::coroutines::coroutine< void() > & ca)
void fn( coro_t::caller_type & ca)
{
    X x;

    for ( int  = 0;; ++i)
    {
        std::cout << "fn(): " << i << std::endl;
        // transfer execution control back to main()
        ca();
    }
}

int main( int argc, char * argv[])
{
    {
        coro_t c( fn,
                  boost::coroutines::attributes(
                    boost::ctx::default_stacksize(),
                    boost::coroutines::stack_unwind) );

        c();
        c();
        c();
        c();
        c();

        std::cout << "c is complete: " << std::boolalpha << c.is_complete() << "\n";
    }

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}

output:
    X()
    fn(): 0
    fn(): 1
    fn(): 2
    fn(): 3
    fn(): 4
    fn(): 5
    c is complete: false
    ~X()
    Done
[Important] Important

You must not swallow boost::coroutines::detail::forced_unwind exceptions!

FPU preserving

Some applications do not use floating-point registers and can disable preserving fpu registers for performance reasons.

[Note] Note

According to the calling convention the FPU registers are preserved by default.


PrevUpHomeNext