Click here to Skip to main content
15,902,939 members
Articles / Programming Languages / C++
Tip/Trick

Generators in C++

Rate me:
Please Sign up or sign in to vote.
3.90/5 (20 votes)
17 Aug 2015BSD2 min read 98.2K   25   12
The way to add generators with yield to C++

Introduction

As we know, iterators in C++ is a good but not a perfect abstraction. The concept of foreach() (D, Python, Ruby, etc.) appears as a more generic solution. At least, foreach() does not require an artificial iterator::end() to be defined for the collection.

The foreach() abstraction can be imagined as some function/object that returns the next value of collection/sequence each time it gets invoked. Such functions are known as generators.

The proposed implementation of the generator/yield feature is provided below in full.

Background

This version of generator() for C++ is based on the bright idea of Simon Tatham - "coroutines in C". In particular, on the idea of using switch/case for this implementation.

Declaring a Generator

To declare a generator, you will use $generator, $yield, $emit, and $stop "keywords" that are macro definitions in fact.

And here is a typical implementation of a generator that emits numbers from 10 to 1 in descending order:

C++
include "generator.h"

$generator(descent)
{
   // place for all variables used in the generator
   int i; // our counter

   // place the constructor of our generator, e.g. 
   // descent(int minv, int maxv) {...}
   
   // from $emit to $stop is a body of our generator:
    
   $emit(int) // will emit int values. Start of body of the generator.
      for (i = 10; i > 0; --i)
         $yield(i); // a.k.a. yield in Python,
                    // returns next number in [1..10], reversed.
   $stop; // stop, end of sequence. End of body of the generator.
};

Having such a descending generator declared, we will use it as:

C++
int main(int argc, char* argv[])
{
  descent gen;
  for(int n; gen(n);) // "get next" generator invocation
    printf("next number is %d\n", n);
  return 0;
}

The gen(n) thing is in fact an invocation of the bool operator()(int& v) method defined "under the hood" of our generator object. It returns true if the parameter v was set, and false if our generator cannot provide more elements - was stopped.

As you may see, for(int n; gen(n);) looks close enough to the construction for(var n in gen) used in JavaScript for exactly the same purpose. Expressiveness is the beauty of the approach.

generator.h

And here is the source code of the generator implementation:

C++
#ifndef __generator_h__
#define __generator_h__

// generator/continuation for C++
// author: Andrew Fedoniouk @ terrainformatica.com
// idea borrowed from: "coroutines in C" Simon Tatham,
//   http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

class _generator
{
protected:
  int _line;
public:
  _generator():_line(0) {}
};

#define $generator(NAME) struct NAME : public _generator

#define $emit(T) bool operator()(T& _rv) { \
                    switch(_line) { case 0:;

#define $stop  } _line = 0; return false; }

#define $yield(V)     \
        do {\
            _line=__LINE__;\
            _rv = (V); return true; case __LINE__:;\
        } while (0)
#endif

That is a bit cryptic, but if you would read the original article of Simon Tatham, then you will get an idea of what is going on here.

Limitations of the Approach

One obvious limitation - $yield cannot be placed inside a switch as $emit() declares a switch by itself.

History

This approach of making generators in C++ was originally published in two articles in my blog:

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Founder Terra Informatica Software
Canada Canada
Andrew Fedoniouk.

MS in Physics and Applied Mathematics.
Designing software applications and systems since 1991.

W3C HTML5 Working Group, Invited Expert.

Terra Informatica Software, Inc.
http://terrainformatica.com

Comments and Discussions

 
QuestionNice trick Pin
rrrado24-Aug-15 0:49
rrrado24-Aug-15 0:49 
AnswerRe: Nice trick Pin
c-smile24-Aug-15 10:40
c-smile24-Aug-15 10:40 
SuggestionIdea for a C++11 generator Pin
jaybus5618-Aug-15 4:28
jaybus5618-Aug-15 4:28 
I think I found a real C++11 solution (or should I call it "abuse" Wink | ;-) ) without macros that seems to work (at least) with GCC.

C++
// Headers necessary for this example
#include <iostream>
#include <string>

// My generator base class
#include <type_traits>
#include <iterator>

template <typename T>
class Generator
{
public:
  class Result: public std::iterator<std::forward_iterator_tag,T> {
  public:
    Result( Generator* g): m_g(g) {}
    T& operator *() { return m_g->m_t;}
    Result& operator++() { m_g->Proceed(); return *this;}
    static Result& End() { static Result r(0); return r;}
    bool operator!= (const Result& rhs) { return m_g->NotDone();}
  private:
    Generator* m_g;
  };
  friend class Result;

  Generator(const T& t) : m_t(t), m_r(this) {}

  virtual Result& begin() { return m_r;}
  Result& end()   { return m_r.End();}

protected:
  virtual void Proceed() = 0;
  virtual bool NotDone() = 0;
  typename std::remove_const<T>::type m_t;
  Result m_r;
};

// An inheritor generating numerics
// (similar to the original article)
class MyGenI :public Generator<int>
{
public:
  MyGenI( int i) : Generator<int>(i) {}
  virtual void Proceed() { --m_t;}
  virtual bool NotDone() { return m_t>-5;}
};

// An inheritor modifying a string
class MyGenS :public Generator<std::string>
{
public:
  MyGenS(const std::string& s) : Generator<std::string>(s) {}
  virtual void Proceed() { m_t += m_t[cntr]+1; ++cntr;}
  virtual bool NotDone() { return cntr < 10;}
private:
  int cntr = 0;
};

// An inheritor generating a string from console input
class MyGenC :public Generator<std::string>
{
public:
  MyGenC(std::string s) : Generator<std::string>(s) {}
  virtual void Proceed()
  {
    std::cin >> m_c;
    m_t += m_c;
  }
  virtual bool NotDone() { return !std::cin.fail();}
  virtual Result& begin()
  {
    if ( m_t.empty()) {
      std::cin >> m_c;
      m_t += m_c;
    }
    return m_r;
  }
private:
  char m_c;
};

// The test
int main(int argc, char* argv[])
{
  std::cout << "numeric\n";
  auto genI = MyGenI{5};
  for(auto i: genI)
    std::cout << "next value is " << i << std::endl;

  std::cout << "\nstring\n";
  auto genS = MyGenS{"a"};
  for(auto s: genS)
    std::cout << "next value is " << s << std::endl;

  std::cout << "\nconsole\n";
  auto genC = MyGenC{""};
  for(auto s: genC)
    std::cout << "next value is " << s << std::endl;

  return 0;
}


What do you think of this? Of course I "abuse" the mechanism of range for-loops here and maybe in future versions or other implementations of C++ this won't work anymore. But the usage and definition of the inheritors look so natural to me.

But this is just an idea and maybe somebody finds a much better solution... Smile | :)
Ignorantia non est argumentum.


modified 18-Aug-15 12:40pm.

GeneralRe: Idea for a C++11 generator Pin
c-smile20-Aug-15 14:55
c-smile20-Aug-15 14:55 
AnswerRe: Idea for a C++11 generator Pin
jaybus5621-Aug-15 3:31
jaybus5621-Aug-15 3:31 
QuestionWasn't this a tip? Pin
Nelek16-Aug-15 22:35
protectorNelek16-Aug-15 22:35 
AnswerRe: Wasn't this a tip? Pin
c-smile17-Aug-15 13:51
c-smile17-Aug-15 13:51 
GeneralRe: Wasn't this a tip? Pin
Nelek18-Aug-15 0:24
protectorNelek18-Aug-15 0:24 
GeneralRe: Wasn't this a tip? Pin
Nelek18-Aug-15 0:28
protectorNelek18-Aug-15 0:28 
GeneralMy vote of 5 Pin
enobayram11-Nov-12 23:13
enobayram11-Nov-12 23:13 
QuestionWhat's the use? Pin
Roland Pibinger21-Sep-08 1:32
Roland Pibinger21-Sep-08 1:32 
AnswerRe: What's the use? Pin
c-smile21-Sep-08 7:47
c-smile21-Sep-08 7:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.