C++ Equivalent of C# Yield?
Asked Answered
Z

11

51
public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Is there a way with template trick (or other) to get the same syntax in c++?

Zampardi answered 27/8, 2011 at 10:4 Comment(3)
Raymond Chen broke down what yield does behind the scenes in blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx.Nanna
@Nanna The link above is unreachableSmallminded
@MichaelIV Microsoft migrates their blogs every 5 years or so which breaks old links. Here you go: devblogs.microsoft.com/oldnewthing/20080812-00/?p=21273Nanna
K
33

Take a look at boost::Coroutine. It does what you want. http://www.crystalclearsoftware.com/soc/coroutine/index.html#coroutine.intro

Example from tutorial

http://www.crystalclearsoftware.com/soc/coroutine/coroutine/tutorial.html

int range_generator(generator_type::self& self, int min, int max) 
{
     while(min < max)
         self.yield(min++);
     self.exit();
}
Kktp answered 27/8, 2011 at 10:9 Comment(4)
+1, that's really interesting and I have little/no idea how self.exit() is a legitimate replacement for a return statement. (I have my suspicions it's some horrific abuse of exceptions or longjmp, but I'm not sure I want to know!)Actinomycosis
Boost.Coroutine is implemented in assembly, and through OS calls on platforms that support "Fibers". It is not implemented in pure C++.Alonaalone
I do not see coroutines in the list of boost libraries on the official site. Any pointers?Resonator
If this is for Win32, please, please, please understand that using Fibers in any code is an extremely advanced topic, and seeing libraries which effectively hide away the Fiber code is really scary. There's a whole boatload of Win32 APIs which don't work in the presence of Fibers, or more scarily don't work as expected. For example locks in Win32 are based off the thread id - this means for Fibers, if you take a lock then yield, another Fiber running on your thread can also successfully take the lock aswell! So unless you're really careful it can bite you hard.Shoebill
K
17

Coroutines are in the standard library since C++20 and uses co_yield instead of yield.

See also: What are coroutines in C++20?

There are some example usages in the first link: (the second one is probably what you're looking for)

  • uses the co_await operator to suspend execution until resumed

    task<> tcp_echo_server() {
       char data[1024];
       while (true) {
          size_t n = co_await socket.async_read_some(buffer(data));
          co_await async_write(socket, buffer(data, n));
       }
    }
    
  • uses the keyword co_yield to suspend execution returning a value

    generator<int> iota(int n = 0) {
       while (true)
          co_yield n++;
    }
    
  • uses the keyword co_return to complete execution returning a value

    lazy<int> f() {
       co_return 7;
    }
    
Kirovabad answered 26/12, 2019 at 1:18 Comment(2)
This is for the new cool kidsTurman
Yes, this how it looks like in modern C++: github.com/ohhmm/overheat/blob/…Collard
E
15

You can always code this by hand. Truthfully, yield really seems like sugar coating to me (and co-routines too).

What a coroutine is, really ? Some state bundled up together with:

  • one function to create it (isn't it a constructor ?)
  • one function to move to the next state (isn't it operator++, traditionally ?)

In C++, it's called an InputIterator, and can be arbitrarily fat.

So, it's true that the syntax won't be as pretty, but this should do, just with the Standard Library:

static std::array<int, 6> const Array = {{1, 2, 4, 8, 16, 16777216}};

class Integers: public std::iterator<std::input_iterator_tag,
                                      int, ptrdiff_t, int const*, int>
{
public:
  Integers(): _index(0) {}

  operator bool() const { return _index < Array.size(); }

  Integers& operator++() { assert(*this); ++_index; return *this; }
  Integers operator++(int) { Integers tmp = *this; ++*this; return tmp; }

  int operator*() const { assert(*this); return Array[_index]; }
  int const* operator->() const { assert(*this); return &Array[_index]; }

private:
  size_t _index;
}; // class Integers

And obviously, since you decide exactly what state is stored, you decide if all is pre-computed or if part (or whole of it) is lazily computed, and possibly cached, and possibly multi-threaded, and ... you got the idea :)

Enscroll answered 27/8, 2011 at 10:47 Comment(13)
I don't see why "sugar coating" is such a bad thing. If you go down to it, a class is also nothing more than sugar coating, the same goes for loops and so on. And the obvious problem with the hands on approach is, that you basically have to write an arbitrarily complex state machine (and I can think of several real world applications where this wouldn't be that easy)Preparatory
@Voo: sugar coating introduces complexity, simply put -> there is more to learn. The OP asked about yield in C++, my take is that instead of "porting" C# syntax into C++, it's best to reflect on what it's doing and find what's idiomatic in C++. A co-routine is nothing more that an InputIterator.Enscroll
I don't agree with "introduces complexity" - generator semantics are simple and easy to understand in my experience (and if there's one language that doesn't follow the "simplest syntax possible" approach it's c++!). Also it's not C# syntax, but a well known concept in CS, implemented in lots of languages (and certainly not the same as an InputIterator!). Implementing a state machine manually for some function is in many cases highly non-trivial. Eg try implementing this with an InputerIterator - certainly harder to understandPreparatory
@Voo: Then we are of different opinion, I prefer a lean core language :) (and readily agree that C++ could be trimmed down) Your example is a nice approach in Python, in C++, I'd trust the Standard Library though, and use std::next_permutation. That is what idiomatic is about in my opinion.Enscroll
Matthieu, what's a for loop but sugar coating over a while loop? What's a switch but a cascade of if? Syntactic sugar isn't necessarily a bad thing, for without it we'd still punch hex op codes directly into memory. It's only a question of where you draw the line. You seem to draw it somewhere between a language with only one loop statement plus one branch statement and a language including yield. Others include yield. Me, I have used it, and see the point of it, but could live either with or without it.Debouchment
@sbi: I have used it too, in Python. The issue with yield is that it transforms a function into a stateful object; there is also an issue with the possibility to pass an argument to the function object to be reinjected at the point where yield suspended the execution (and what if there are several yields expecting differnet arguments ?), etc... All in all, I think the keyword hides too much of what's going on under the hood, and it makes it more difficult to understand what's going on. Too much sugar coating, imho.Enscroll
@Matthieu: Lambda also does such a transformation and hides what's under the hood. Are you opposed to that, too? :)Debouchment
@sbi: not as much, a lambda is a simple functor with easy semantics to capture state for the surrounding environment, not a full blown state machine (I am thinking multiple yield in one function, with the ability of injecting values at each suspension).Enscroll
@Matthieu M. Yes and looking at the implementation of next_permutation pretty much proves my point about it being several times more complex (after all that was only an example not the only use case). And I've never heard of reinjecting arguments into a suspended function - and not one of the languages listed on Wikipedia seem to have that functionality. And isn't the whole point of "sugar coating" hiding stuff that can be done by the compiler but would be quite complex for the programmer? Seems to me c++, contrary to c, abstracts quite a lot away.Preparatory
@Voo: Python supports it (it didn't in the original implementation, but it's been improved since). You can read more about it in the PEP 342 (python.org/dev/peps/pep-0342), unfortunately this does not work so well with C++ static typing (as soon as you'd need multiple yield expecting different types).Enscroll
@Matthieu M. Interesting, didn't know that - always nice to learn something new (thanks!). I assume this could be useful in some cases but I agree that this adds complexity to the rather simple concept (you can't have arguments for the first call and so on). To me this seems more like a large step towards coroutines.Preparatory
@Matthieu: So the transformations done for lambda functions are Ok, the ones needed for yield wouldn't. Isn't this just what I said? It all boils down to where you draw the line. :)Debouchment
They're sugar in C# too. But also, isn't all programming "just" sugar syntax for binary? It takes what is normally lines of complicated code into a single keyword. This isn't a bad thingSwaddle
K
13

In C++14, you can mimic yield this way:

auto&& function = []() { 
    int i = 0; 
    return [=]() mutable { 
        int arr[] = { 1, 2, 4, 8, 16, 16777216}; 
        if (i < 6) 
            return arr[i++]; 
        return 0; 
    }; 
}();

A live example is available at http://ideone.com/SQZ1qZ

Knighton answered 12/7, 2016 at 17:44 Comment(3)
I didn't but the live example can easily fit in your answer without displaying ads from ideone.com.Tewfik
Isn't the purpose of yield to prevent the series of objects (int[] in this case) from being immediately put into memory?Parkinson
The example is still valid, precalculating an array is not required, take the next example. #include <iostream> int main() { auto&& function = [](int i0) { int i = i0; return [=]() mutable { i *= 2; return i;}; }; auto fn = function(5); for ( unsigned long i = 0; i != 10; ++i ) std::cout << "\t" << fn() << "\t|"; std::cout << "\n"; return 0; }Lorou
S
4

Here is ASM "roll your own" version : http://www.flipcode.com/archives/Yield_in_C.shtml

#include <stdio.h
#include <conio.h
#include <iostream.h


//
// marks a location in the program for resume
// does not return control, exits function from inside macro
//
// yield( x, ret )
//      x : the 'name' of the yield, cannot be ambiguous in the
//          function namespace
//    ret : the return value for when yield() exits the function;

//          must match function return type (leave blank for no return type)

#define yield(x,ret)                            \
    {                                           \
        /* store the resume location */         \
        __asm {                                 \
            mov _myStaticMkr,offset label_##x   \
        }                                       \
                                                \
        /* return the supplied value */         \
        return ret;                             \
    }                                           \
    /* our offset in the function */            \
    label_##x:



//
// resumes function from the stored offset, or
// continues without notice if there's not one
// stored
//
// resume()
//   <void

#define resume()                        \
    /* our stored offset */             \
    static _myStaticMkr=0;              \
                                        \
    /* test for no offset */            \
    if( _myStaticMkr )                  \
    {                                   \
        /* resume from offset */        \
        __asm                           \
        {                               \
            jmp _myStaticMkr            \
        }                               \
    }


// example demonstrating a function with an int return type
// using the yield() and resume() macros
//
// myFunc()
//   <void

int myFunc()
{
    resume();

    cout << "1\n";

    yield(1,1);

    cout << "2\n";

    yield(2,1);

    cout << "3\n";

    yield(3,1);

    cout << "4\n";

    return 0;
}



// main function
//
// main()
//   <void

void main( void )
{
    cout << "Yield in C++\n";
    cout << "Chris Pergrossi\n\n";

    myFunc();

    do

    {
        cout << "main()\n";
        cout.flush();
    } while( myFunc() );

    cout.flush();

    getch();
}


/*

// example demonstrating a function with no return type
// using the yield() and resume() macros
//
// myFunc()
//   <void

void myFunc()
{
    resume();

    cout << "1\n";

    yield(1);

    cout << "2\n";

    yield(2);

    cout << "3\n";

    yield(3);

    cout << "4\n";

    return;
}



// main function
//
// main()
//   <void

void main( void )
{
    cout << "Yield in C++\n";
    cout << "Chris Pergrossi\n\n";

    myFunc();

    for( int k = 0; k < 4; k ++ )
    {
        cout << "main()\n";
        cout.flush();

        myFunc();
    }

    cout.flush();

    getch();
}

*/  
Smallminded answered 29/8, 2013 at 10:47 Comment(5)
Very nice, but is this cross-platform?Kleinstein
Nobody should use this, if you do, remove it. This involves #UB as your stack data can be overwritten by anything you do until you resume.Turman
@AlexisPaques That's not undefined behaviour, that's just having to manually deal with the stack, which is pretty much what we'd expect if we use __asm.Subphylum
All local variables are broken here. A proper implementation needs to save the context to be able to resume it. It is not the case here.Turman
thisisub.godbolt.org/z/fh87sd1c4 - Feel free to check by yourself :)Turman
E
2

If all what you need is just foreach-like stuff, then following syntax is available in C++:

#define GENERATOR(name) \
struct name \
{ \
    template<typename F> \
    void operator()(F yield) \
/**/
#define _ };

template<typename Gen>
struct Adaptor
{
    Gen f;
    template<typename C>
    void operator*(C cont)
    {
        f(cont);
    }
};

template<typename Gen>
Adaptor<Gen> make_adaptor(Gen gen)
{
    return {gen};
}

#define FOREACH(arg, gen) make_adaptor(gen) * [&](arg)

#include <iostream>
using namespace std;

GENERATOR(integers)
{
    yield(1);
    yield(2);
    yield(4);
    yield(8);
    yield(16777216);
}_

int main()
{
    FOREACH(int i, integers())
    {
        cout << i << endl;
    };
}

Live Demo

If you need a little bit of coroutine "power", then you can try stackless coroutines.

Or if you need full power - then go with stackful coroutines. There is Boost.Coroutine library which implements stackful coroutines for different platforms.

Elmira answered 2/11, 2013 at 21:55 Comment(0)
Z
1

An try to implement yield in c++ coroutine

Zampardi answered 3/9, 2011 at 8:5 Comment(0)
F
1

If you write static unsigned int checkpoint = 0;, make all your variables static, switch (checkpoint), set each case: goto to some label, above each return set checkpoint to unique value, and below define label, and at the end of the function set checkpoint to zero, and all static variables to their default value, and at last return the end value of the function. If you do all this then the function becomes enumerable and iterative. The two lines you add above and below each return line, makes the return command to behave like yield return. goto allows you to continue and resume where you left off, and static integer variable, like checkpoint, help you to remember where you stopped, from where to continue/resume and where to go. You test it's values with switch case statements. Making all other variables static, is to save their value to the next call, so in the next call, their value won't be reset!

Here for example:

#define PowerEnd INT_MIN
int Power(int number, int exponent)
{
    static unsigned int checkpoint = 0;
    static int result = 1, i = 0;
    switch (checkpoint)
    {
        case 1: goto _1;
    }
    for (i = 0; i < exponent; i++)
    {
        result *= number;
        checkpoint = 1;
        return result;
        _1:;
    }
    checkpoint = 0;
    result = 1;
    i = 0;
    return PowerEnd;
}
void main()
{
    while (true)
    {
        int result = Power(2, 8);
        if (result == PowerEnd)
            break;
        cout << result << endl;
    }
    //to print only the first 4 results (if there are at least 4 results) then
    for (int i = 0; i < 4; i++)
    {
        int result = Power(2, 8);
        if (result == PowerEnd)
            break;
        cout << result << endl;
    }
}

The above program produces the following output:

2 4 8 16 32 64 128 256 2 4 8 16

Folderol answered 20/5, 2016 at 22:25 Comment(0)
T
1

Something similar is proposed for C++17 and there is already an experimental implementation in Visual C++ 2015. Here's a good overview talk from Gor Nishanov, one of the main authors of the proposal.

Tat answered 20/5, 2016 at 22:40 Comment(0)
G
0
#include <setjmp.h>

class superclass
{
public:
    jmp_buf jbuf;
public:
    virtual int enumerate(void) { return -1; }
};

class subclass: public superclass
{
public:
    int enumerate()
    {
        static int i;
        static bool b = false;

        if(b) 
            longjmp(jbuf, 1);

        for(b = true, i = 0; i < 5; (i)++)
        {
            printf("\ndoing stuff: i = %d\n", i);

            if(setjmp(jbuf) != 1) 
                return i;    
        }
        return -1;
    }
};

To use the code...

int iret; 
subclass *sc;

sc = new subclass();
while((iret = sc->enumerate()) != -1)
{
    printf("\nsc->enumerate() returned: %d\n", iret);
}

Just got this working; it seems quite simple now, although I had a few false starts with it :)

Geof answered 1/1, 2014 at 18:39 Comment(0)
F
-2

You can of course always write your own iterators and return from them whatever you desire, but why would you want to? In the given example, why not simply put your values into a container like vector and iterate over that?

Filigreed answered 27/8, 2011 at 10:9 Comment(4)
Consider a situation were the values are to be computed. You might want to have lazy evaluation of the sequence of values, which is what the code shown does. You could write a function that returns an infinite list that way.Perseverance
@Filigreed In a real application, there would be more than a few integers and they may not be integers at all, but something much more expensive. Maybe the caller just wants to find the element that fulfills certain condition - storing elements in a container would not just waste space, but also the time because elements that are after the desired element would be calculated unnecessarily. Also, std::vector needs to go through reallocate/copy routine to increase its size (unless known in advance, which in general case you don't need to know for iterator blocks).Aultman
@Filigreed One nice example where a generator makes for an extremely nice and simple function is eg this - it's python, but you should get the gist of it anyhow.Preparatory
In Python I am used to yield a lot and miss it in C++. The best example is where I want to hide the MYSQL implementation. For example, the top level wants to know tables in a restaurant, but not see the SQL implementation:: for (x : sql.getAllTables()) ... and the function sql.getAllTables() { sql.query("select id, name from some_table order by name", for (x in result) yield one_table } ..Billie

© 2022 - 2024 — McMap. All rights reserved.