Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Generator

Class generator

A generator is a simple coroutine that can be used to control the iteration behaviour of a loop. It generates a sequence of values, but instead of returning them at once, a generator behaves like an iterator yielding a values one at the time if it is called.

Each instance of generator represents a context (CPU registers and stack space) of execution or not-a-generator. Objects of type generator are moveable but not copyable and can be returned by a function.

boost::coro::generator< int > make_generator();

void f()
{
    boost::coro::generator< int > c( make_generator() );
    c();
}
[Note] Note

Boost.Move is used to emulate rvalue references.

[Warning] Warning

If generator is used in a multithreaded application, it can migrated between threads, but must not reference thread-local storage.

[Note] Note

If fiber-local storage is used on Windows, the user is responsible for calling ::FlsAlloc(), ::FlsFree().

Executing a generator

A new generator is created from a callable object (known as the generator-function). The stack size, stack unwinding and floating-point preserving behavior are determined by additional arguments.

The generator constructor uses the StackAllocator concept from Boost.Context to allocate an associated stack, and the destructor uses the same StackAllocator concept to deallocate the stack. The default StackAllocator concept is StackAllocator, but a custom stack-allocator can be passed to the constructor (see documentation of Boost.Context).

The first argument of generator-function must be of type generator<>::self_t, used for yielding the active generator.

typedef boost::coro::generator< int > gen_t;

void f( gen_t::self_t & self, int i)
{
    int x;
    ...
    self.yield( x);
    ...
}

gen_t g( boost::bind( f, _1, 7) );
int y = g();

The generator-function, as well as its arguments, if any, are copied into the generator's state. If a reference is required, use boost::ref.

Usally a generator is called inside loops. The generator-function executed until generator<>::self_t::yield() is called. The value passed ot generator<>::self_t::yield() is used as the generated value. The next time the same generator is called in a subsequent iteration, the execution of the generator-function is resumed after generator<>::self_t::yield(), until generator<>::self_t::yield() is called again. generator-function can be terminated by generator<>::self_t::yield_break(), at which time the loop enclosing the generator is terminated.

typedef boost::coro::generator< int >    gen_t;

int power( gen_t::self_t & self, int number, int exponent)
{
    int counter = 0, result = 1;
    while ( counter++ < exponent)
    {
        result = result * number;
        self.yield( result);
    }
    self.yield_break();
}

int main()
{
    {
        gen_t pw( boost::bind( power, _1, 2, 8) );
        while ( pw) {
            std::cout << pw() <<  " ";
        }
    }

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

    return EXIT_SUCCESS;
}

output:
    2 4 8 16 32 64 128 256
    Done
[Note] Note

The maximum number of arguments of generator-function is 10.

generator-function is invoked the first time inside the constructor of generator. generator<>::operator()() can be called as long as generator remains valid, e.g. generator<>::operator unspecified_bool_type() returns true. If generator-function can not return valid values anymore generator<>::self_t::yield_break() should be called. This function returns the execution cotnrol back to the caller and sets the generator to be complete (is_complete() returns true). In this case generator<>::operator()() does not throw an exception like coroutine In this case generator<>::operator()() does not throw an exception like coroutine. Of curse the generator-function can be exited with a return expression.

typedef boost::coro::generator< int > gen_t;

int fn( gen_t::self_t & self, 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()
        self.yield( i);

        // coroutine<>::operator()() was called
        // execution control transfered back from main()    
    }
    self.yield_break();
}

int main( int argc, char * argv[])
{
    gen_t g( boost::bind( fn, _1, 7) );

    // yield was called so we returned
    while ( g)
    {
        // execution control is transfered to g
        int x = g();
        std::cout << "main(): generated == " << x << std::endl;

        // yield() was called within fn()
    }
    // yield_break() was called within fn()

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

    return EXIT_SUCCESS;
}

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

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

[Note] Note

In contrast to threads, which are preemtive, generator switches are cooperative (programmer controls when switch will happen). The kernel is not involved in the coroutine switches.

Exceptions in generator-function

A excepton thrown by generator-function is transfered by exception-pointer (see documentation of Boost.Exception for details and requirements) and will be re-thrown by generator<>::operator()().

[Important] Important

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

try
{
    // code that might throw
}
catch( forced_unwind)
{
    throw;
}
catch(...)
{
    // possibly not rethrow 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 (unwind stack as default).

Stack unwinding assumes the following preconditions:

After unwinding, a generator is complete.

[Important] Important

You must not swallow forced_unwind exceptions!

FPU preserving

Some applications do not use floating-point registers and can disable preserving fpu registers for perfromance reasons (see chapter performance).

[Note] Note

In order to be combatible to the ABIs the FPU registers are preserved by default.


PrevUpHomeNext