Skip to content

Commit a47f557

Browse files
committed
v. 1.0
1 parent b045bd4 commit a47f557

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,64 @@
11
# cpp-generators
22
Generators/corroutines for C++
3+
4+
## Introduction
5+
6+
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.
7+
8+
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.
9+
10+
Generators are functional objects that persist internal state between invocations thus they can be considered as corroutines too.
11+
12+
## Background
13+
14+
This version of $generator's for C++ is based on the bright [idea of Simon Tatham - "coroutines in C"](http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html). In particular, on the idea of using `switch`/`case` for the implementation.
15+
16+
## Declaring a Generator
17+
18+
To declare a generator, you will use `$generator`, `$yield`, `$emit`, and `$stop` "keywords" that are macro definitions in fact.
19+
20+
Here is a typical implementation of a generator. In this particular example our generator will emit numbers from 10 to 1 in descending order:
21+
22+
```cpp
23+
include "generator.hpp"
24+
25+
$generator(descent)
26+
{
27+
// place for all variables used in the generator
28+
int i; // our counter
29+
30+
// place the constructor of our generator, e.g.
31+
// descent(int minv, int maxv) {...}
32+
33+
// from $emit to $stop is a body of our generator:
34+
35+
$emit(int) // will emit int values. Start of body of the generator.
36+
37+
for (i = 10; i > 0; --i)
38+
$yield(i); // a.k.a. yield in Python,
39+
// returns next number in [1..10], reversed.
40+
$stop; // stop, end of sequence. End of body of the generator.
41+
};
42+
```
43+
44+
Having such a descending generator declared, we will use it as:
45+
46+
```cpp
47+
int main(int argc, char* argv[])
48+
{
49+
descent gen;
50+
for(int n; gen(n);) // "get next" generator invocation
51+
printf("next number is %d\n", n);
52+
return 0;
53+
}
54+
```
55+
56+
The `gen(n)` there 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.
57+
58+
## Limitations of the Approach
59+
60+
`$yield` cannot be placed inside a `switch` statement as `$emit()` declares a `switch` by itself.
61+
62+
## License
63+
64+
The generator.hpp is licensed under [The BSD License](https://opensource.org/licenses/bsd-license.php).

generator.hpp

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#ifndef __generator_hpp__
2+
#define __generator_hpp__
3+
4+
//|
5+
//| micro generator/coroutine/continuation for C++
6+
//| author: Andrew Fedoniouk @ sciter.com
7+
//| idea borrowed from: "coroutines in C" by Simon Tatham,
8+
//| http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
9+
//|
10+
11+
namespace generator
12+
{
13+
//++ coroutine, generator, continuation for C++
14+
15+
struct instance
16+
{
17+
protected:
18+
int _line;
19+
public:
20+
instance() { rewind(); }
21+
void rewind() { _line = 0; } // restart the generator
22+
23+
private:
24+
instance( const instance& ); // non-copyable
25+
instance& instance=( const instance& ); // non-assignable
26+
};
27+
28+
// Declaration
29+
#define $generator(NAME) struct NAME : public generator::instance
30+
31+
// Start of generator's body, value[s] emmiter.
32+
// Shall end with $stop and contain at least one $emit[n]
33+
#define $emit(T) bool operator()(T& _rv) { switch(_line) { case 0:;
34+
#define $emit2(T1,T2) bool operator()(T1& _rv1, T2& _rv2) { switch(_line) { case 0:;
35+
#define $emit4(T1,T2,T3) bool operator()(T1& _rv1, T2& _rv2, T3& _rv3 ) { switch(_line) { case 0:;
36+
#define $emit4(T1,T2,T3,T4) bool operator()(T1& _rv1, T2& _rv2, T3& _rv3, T4& _rv4 ) { switch(_line) { case 0:;
37+
38+
// yields next value(s)
39+
#define $yield(V) { _line=__LINE__; _rv = (V); return true; case __LINE__: _line=__LINE__; }
40+
#define $yield2(V1,V2) { _line=__LINE__; _rv1 = (V1); _rv2 = (V2); return true; case __LINE__: _line=__LINE__; }
41+
#define $yield3(V1,V2,V3) { _line=__LINE__; _rv1 = (V1); _rv2 = (V2); _rv3 = (V3); return true; case __LINE__: _line=__LINE__; }
42+
#define $yield4(V1,V2,V3,V4) { _line=__LINE__; _rv1 = (V1); _rv2 = (V2); _rv3 = (V3); _rv4 = (V4); return true; case __LINE__: _line=__LINE__; }
43+
44+
// end of value[s] emmiter
45+
#define $stop } _line = 0; return false; }
46+
47+
//-- coroutine, generator, continuation for C++
48+
49+
}
50+
51+
#endif

0 commit comments

Comments
 (0)