Equivalent C++ to Python generator pattern Equivalent C++ to Python generator pattern python python

Equivalent C++ to Python generator pattern


Generators exist in C++, just under another name: Input Iterators. For example, reading from std::cin is similar to having a generator of char.

You simply need to understand what a generator does:

  • there is a blob of data: the local variables define a state
  • there is an init method
  • there is a "next" method
  • there is a way to signal termination

In your trivial example, it's easy enough. Conceptually:

struct State { unsigned i, j; };State make();void next(State&);bool isDone(State const&);

Of course, we wrap this as a proper class:

class PairSequence:    // (implicit aliases)    public std::iterator<        std::input_iterator_tag,        std::pair<unsigned, unsigned>    >{  // C++03  typedef void (PairSequence::*BoolLike)();  void non_comparable();public:  // C++11 (explicit aliases)  using iterator_category = std::input_iterator_tag;  using value_type = std::pair<unsigned, unsigned>;  using reference = value_type const&;  using pointer = value_type const*;  using difference_type = ptrdiff_t;  // C++03 (explicit aliases)  typedef std::input_iterator_tag iterator_category;  typedef std::pair<unsigned, unsigned> value_type;  typedef value_type const& reference;  typedef value_type const* pointer;  typedef ptrdiff_t difference_type;  PairSequence(): done(false) {}  // C++11  explicit operator bool() const { return !done; }  // C++03  // Safe Bool idiom  operator BoolLike() const {    return done ? 0 : &PairSequence::non_comparable;  }  reference operator*() const { return ij; }  pointer operator->() const { return &ij; }  PairSequence& operator++() {    static unsigned const Max = std::numeric_limts<unsigned>::max();    assert(!done);    if (ij.second != Max) { ++ij.second; return *this; }    if (ij.first != Max) { ij.second = 0; ++ij.first; return *this; }    done = true;    return *this;  }  PairSequence operator++(int) {    PairSequence const tmp(*this);    ++*this;    return tmp;  }private:  bool done;  value_type ij;};

So hum yeah... might be that C++ is a tad more verbose :)


In C++ there are iterators, but implementing an iterator isn't straightforward: one has to consult the iterator concepts and carefully design the new iterator class to implement them. Thankfully, Boost has an iterator_facade template which should help implementing the iterators and iterator-compatible generators.

Sometimes a stackless coroutine can be used to implement an iterator.

P.S. See also this article which mentions both a switch hack by Christopher M. Kohlhoff and Boost.Coroutine by Oliver Kowalke. Oliver Kowalke's work is a followup on Boost.Coroutine by Giovanni P. Deretta.

P.S. I think you can also write a kind of generator with lambdas:

std::function<int()> generator = []{  int i = 0;  return [=]() mutable {    return i < 10 ? i++ : -1;  };}();int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

Or with a functor:

struct generator_t {  int i = 0;  int operator() () {    return i < 10 ? i++ : -1;  }} generator;int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

P.S. Here's a generator implemented with the Mordor coroutines:

#include <iostream>using std::cout; using std::endl;#include <mordor/coroutine.h>using Mordor::Coroutine; using Mordor::Fiber;void testMordor() {  Coroutine<int> coro ([](Coroutine<int>& self) {    int i = 0; while (i < 9) self.yield (i++);  });  for (int i = coro.call(); coro.state() != Fiber::TERM; i = coro.call()) cout << i << endl;}


Since Boost.Coroutine2 now supports it very well (I found it because I wanted to solve exactly the same yield problem), I am posting the C++ code that matches your original intention:

#include <stdint.h>#include <iostream>#include <memory>#include <boost/coroutine2/all.hpp>typedef boost::coroutines2::coroutine<std::pair<uint16_t, uint16_t>> coro_t;void pair_sequence(coro_t::push_type& yield){    uint16_t i = 0;    uint16_t j = 0;    for (;;) {        for (;;) {            yield(std::make_pair(i, j));            if (++j == 0)                break;        }        if (++i == 0)            break;    }}int main(){    coro_t::pull_type seq(boost::coroutines2::fixedsize_stack(),                          pair_sequence);    for (auto pair : seq) {        print_pair(pair);    }    //while (seq) {    //    print_pair(seq.get());    //    seq();    //}}

In this example, pair_sequence does not take additional arguments. If it needs to, std::bind or a lambda should be used to generate a function object that takes only one argument (of push_type), when it is passed to the coro_t::pull_type constructor.