Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a generator in C++0x

The python keyword yield has been a great conceptual abstraction for me, allowing me to distill the important parts of an algorithm to human-readable form. We have previously discussed:

Python generators in various languages

where an answer was given for a windows only library in C++. In addition I've found another example using a funky macro expansion in the question:

Generators in C++ — invalid use of nonstatic data member

The edge of my computer science knowledge tells me that a yield function has something to do with co-routines and monads, but I'm not quite how this fits into what C++ or C++0x can accomplish.

It seems that in C++, without the using of a macro-expansion or a windows only fiber (thread), yeild can not be implemented. Is this true? Does the question change with the additional language features of C++0x?

like image 986
Hooked Avatar asked Nov 16 '11 15:11

Hooked


People also ask

Are there generators in C++?

C++ Algorithm generate() function is used to assign the value generated by a function object to each element in a range. The generator function is defined by the user and it is called successively for assigning the numbers.

How do you get a random number between 0 and 1 in C++?

C++ Random Number Between 0 And 1 We can use srand () and rand () function to generate random numbers between 0 and 1. Note that we have to cast the output of rand () function to the decimal value either float or double.

How do you generate a random number with a range in C++?

How to Generate Random Numbers in C++ Within a Range. Similar to 1 and 10, you can generate random numbers within any range using the modulus operator. For instance, to generate numbers between 1 and 100, you can write int random = 1+ (rand() % 100).


3 Answers

You can map yield python mechanism to C++ iterators.

See Boost Function Input Iterator and the example:

The Function Input Iterator allows for creating iterators that encapsulate a nullary function object and a state object which tracks the number of times the iterator has been incremented. A Function Input Iterator models the InputIterator concept and is useful for creating bounded input iterators.

Like the Generator Iterator, the Function Input Iterator takes a function that models the Generator concept (which is basically a nullary or 0-arity function object). Each increment of the function Function Input Iterator invokes the generator function and stores the value in the iterator. When the iterator is dereferenced the stored value is returned.

like image 101
Maxim Egorushkin Avatar answered Oct 04 '22 21:10

Maxim Egorushkin


yield is basically a way to implement a restricted form of coroutines.

If you want to badly enough, you can (as in, "people have") implemented relatively complete coroutines in C using setjmp and longjmp. In C++, you can probably do the same, though I'm not entirely sure. The problem with C++ becomes one of deciding what dtors to execute when. Offhand, I think the answer is that dtors shouldn't be affected by the coroutine usage, but I haven't really thought about it much. Assuming that's correct, then roughly the same code should probably work for C++ as for C.

C++0x adds full support for threads and such. Though it can be clumsy and/or increase overhead, pretty much anything you could hope to do with fibers, you can also do with threads. As such, it would support the idiom quite a bit more directly, so implementation would be quite a bit easier.

like image 23
Jerry Coffin Avatar answered Oct 04 '22 20:10

Jerry Coffin


Hmm I've never used generators myself but from what I understand you want some function that returns an iterator that implements the increment operator ++. Pseudo code:

int f(int x) {
    for(int i = 0; i < x; ++i) {
        yield(i);
    }
}

for(iterator it = make_generator(std::bind(f, 5));
    it != generator_end();
    ++it)
{
    do_something(*it);
}

// iterator specialized for the return type of f
template<typename T>
iterator<T> make_generator(Function<T (void)> f) {
    ...
}

You will need to construct the iterator from pointer to function f, and you can pass any arguments to f by using std::bind.

Now at each iteration you need to effectively call f at some point and the function yield must do some stack manipulation to save the current stack, return the yielded value and restore the stack when the function gets called again. You only need to save the stack up to the point where f is called (no need for entire stack), but this could be tricky to implement. You'll probably have to save the stack pointer before you call f, then make a copy of the contents of (previous stack pointer..current stack pointer) somewhere.

like image 20
Giovanni Funchal Avatar answered Oct 04 '22 22:10

Giovanni Funchal