Saturday, June 5, 2010

Touring C++ Coroutines

In the last couple of months, i have been exposed to C++ Co-routines and i wanted to write about my learning. The library i'm referring to is the Boost.coroutine where it has a family of class templates that wrap function objects in coroutines and these coroutines can be reentered and returned more than once without causing the destruction of automatic objects. Basically, it allow states to be store/retrieve across a function call. I've had the immense pleasure of figuring this stuff out because another favourite scripting language of mine, Python, uses this concept extensively in its 'yield' statement.

There are two important concepts you'll need to comprehend before you can use coroutines effectively in C++. Number 1, the subroutines we're so used to programming (e.g. functions, procedures) lose their state when they return (You'll probably wonder that function objects also save state but there's a difference and that is: function objects stored their state in class member variables while coroutines store the state in the stack as automatic objects - these automatic objects are not destroyed btw which is COOL!). Number 2, coroutines preserved the point of execution and is able to resume execution from where it left off which is unlike function objects

Simple use of Coroutines
One example is the generator function (yep, python has it too) and what it is is really a function that returns a sequence of values instead of returning all of them; this programming concept is very useful in practice in the form of a dynamic prime number generator.

I love the function objects because it allows me to indulge in the world of functional programming to an extent though i know i'm still working in C++. You can pretty much recognize a function object in C++ by looking for the operator()() in the source code and possibly a couple of operators that are overloaded that suits the logic.

Simple example is the generator that generates all numbers (integers) between lower and upper inclusive. e.g. the lower bound is 1 and 4.

class generator {
public:
  generator(int lower, int upper): lb(lower), ub(upper) {}
  int operator()() { return lb++; }
  operator bool() const { return lb < ub; }
private:
  int lb, ub;
};
int main(int argc, char** argv) {
  generator gen(1,4);
 // invokes the operator bool() 
  while(gen)
    // invokes the operator()() - function object! 
    std::cout << gen() << "\n";
}
There's another way and that is using input iterators as generators but IMO, its not that straightforward and simple as opposed to using function objects which i do consider more elegance but then again, i'll iterate a philosophy of mine "whatever that works simply" when it comes to choosing paradigms

So i know i can write generators using function objects and can i do that using coroutines? Sure i can and here's what i found out

// In a .h file, there's the contents 
#ifndef GEN_TEMPLATE_H
#define GEN_TEMPLATE_H

#include 
namespace coro = boost::coroutines;
typedef coro::generator gen_type;
/*
Notice that range_generator body is entered for the first itme when the generator is constructed
(from the main entry point) then at every iteration range_iterator is reentered from 
yield(). In this case range_iterator is reentered when generator::operator++ is invoked.
*/
int range_generator(gen_type::self& self, int min, int max) {
    while ( min < max - 1)
        self.yield(min++);
    self.exit(); // This is used instead
}
#endif
...
...
// In another .cpp file
#ifdef GEN_TEMPLATE_H
    gen_type generator(boost::bind(range_generator, _1, 100, 200) );
    while( generator != gen_type() )
        std::cout << *generator++ << "\n";
#endif 
Now this example is the input iterator fashion of creating a generator function and the creation of the generator is in the statement boost::bind(range_generator, _1, 100, 200) where the boost::coroutines::generator::self is bound to range_generator; another thing is that you can see the generator entry function "yield" and exit function "exit" invoked through the "self" object. The "self" object is used to identify the different instances of a generator.

So i asked myself: "Great, it works on built-ins, how about user defined types?" Yep can be done too and here's what i did, its not great but it'll do for this example

// in some .h file 
#ifndef GEN_TEMPLATE_H
#define GEN_TEMPLATE_H
class test {
public:
    typedef int value_type;
    test(int min) : m_current(min) {}
    test() {}
    test& operator++() {
        m_current++;
        if ( m_current > max ) m_current = -1;
        return *this;
    }
    test& operator++(int) {
        test t(*this);
        ++*this;
        return t;
    }
    friend bool operator<(test& rhs, const test& lhs) {
        if ( rhs.max != lhs.max ) rhs.max = lhs.max; // only invoked the first time
        return rhs.m_current < lhs.m_current;
    }
    friend bool operator==(const test& rhs, const test& lhs) {
        return rhs.m_current == lhs.m_current;
    }
    friend bool operator!=(const test& rhs, const test& lhs) {
        return !(rhs == lhs);
    }
    operator int() { return m_current;}
    int operator* () { return m_current; }
private:
    int m_current;
    int max;
};
#include 
namespace coro = boost::coroutines;

typedef coro::generator gen_type;
/*
Notice that range_generator body is entered for the first itme when the generator is constructed
(from the main entry point) then at every iteration range_iterator is reentered from 
yield(). In this case range_iterator is reentered when generator::operator++ is invoked.
*/
int range_generator(gen_type::self& self, test min, test max) {
    while ( min < max)
        self.yield(min++);
    self.exit(); // This is used instead
}
#endif 
...
...
// in some .c file
#ifdef GEN_TEMPLATE_H
    gen_type generator(boost::bind(range_generator, _1, test(100), test(200)) );
    while( generator != gen_type() )
        std::cout << *generator++ << "\n";
#endif 
Pretty basic stuff there, so it can be done.

Another concept: the producer/consumer pattern
I love this concept because its basically a messaging system and i have Nat Goodspeed & Brad Kittenbrink to thank ;) thanks guys!

Here's how it works (its not perfect code but it works to illustrate my example)

// In some .h file

#ifndef MESSAGE_H
#define MESSAGE_H
#include 
#include 


class Message {
public:
    Message(int msg) : m(msg) { std::cout << "Created:" << m << std::endl; }
    int operator *() { return m; }
    friend std::ostream& operator<<(std::ostream& os, const Message& m);
private:
    int m;
};

std::ostream& operator<<(std::ostream& os, const Message& m) {
    os << m.m << std::endl;
    return os;
}

template 
class MessageQ {
public:
    MessageQ(int capacity = 10) : capacity(capacity) {
        for( int i = 0; i < capacity; ++i) q.push_back(Message(i));
    }
    operator bool() { return q.size() > 0; }
    Message operator *() { Message lastEle = q.back(); q.pop_back(); return lastEle; }
    MessageQ& operator++() {
        if ( q.size() <= 0 ) return false;
        q.pop_back();
        return *this;
    }
private:
    std::vector q;
    int capacity;
};

#endif

#ifdef MESSAGE_H
#include 
namespace coro = boost::coroutines;

typedef coro::generator gen;

const Message& producer(gen::self& self, MessageQ msgq) {
    while (msgq) {
        self.yield(*msgq);
    }
    self.exit();
}

template
void consumer(Producer prod) {
    do {
        std::cout << *prod << std::endl;
    } while( ++prod );
}

#endif
...
...
// and in some other .c file
#ifdef MESSAGE_H
    consumer(gen(boost::bind(producer, _1, MessageQ())));
#endif
So what i've basically done is to create a message queue of sorts and populate it with 10 simple messages and use the generator, in this case the consumer, to iterate through the messages.

0 comments: