30

EDIT, 11 years after I asked this question: I feel vindicated for asking! C++20 finally did something close enough.

The original question follows below.

--

I have been using yield in many of my Python programs, and it really clears up the code in many cases. I blogged about it and it is one of my site's popular pages.

C# also offers yield – it is implemented via state-keeping in the caller side, done through an automatically generated class that keeps the state, local variables of the function, etc.

I am currently reading about C++0x and its additions; and while reading about the implementation of lambdas in C++0x, I find out that it was done via automatically generated classes too, equipped with operator() storing the lambda code. The natural question formed in my mind: they did it for lambdas, why didn't they consider it for support of "yield", too?

Surely they can see the value of co-routines... so I can only guess that they think macro-based implementations (such as Simon Tatham's) as an adequate substitute. They are not, however, for many reasons: callee-kept state, non-reentrant, macro-based (that alone is reason enough), etc.

Edit: yield doesn't depend on garbage collection, threads, or fibers. You can read Simon's article to see that I am talking about the compiler doing a simple transformation, such as:

int fibonacci() {
    int a = 0, b = 1;
    while (true) {
        yield a;
        int c = a + b;
        a = b;
        b = c;
    }
}

Into:

struct GeneratedFibonacci {
    int state;
    int a, b;

    GeneratedFibonacci() : state (0), a (0), b (1) {}

    int operator()() {
        switch (state) {
        case 0:
            state = 1;
            while (true) {
                return a;

        case 1:
                int c = a + b;
                a = b;
                b = c;
            }
        }
    }
}

Garbage collection? No. Threads? No. Fibers? No. Simple transformation? Arguably, yes.

ttsiodras
  • 10,602
  • 6
  • 55
  • 71
  • 1
    Keep in mind that "anything can be done" in languages like C and C++, just because it's easy to emulate manually for a simple example, doesn't make it easy to incorporate into the syntax. Take Boost for example, it does crazy stuff with C++, but the lengths they goto are crazy behind the scenes. Even when ideas in Boost are incorporated into standard C++ they are implemented completely differently. (take a look at unique_ptr, lambdas and variable parameters stuff) – Matt Joiner Oct 05 '10 at 16:10
  • 6
    Because nobody wrote an official proposal for it. – sellibitze Oct 05 '10 at 18:17
  • 1
    Your original transformation of the generator had a bug: it never progressed into state 1. Fixed that and applied Duff's device. –  Oct 06 '10 at 16:53
  • FWIW, your blog post's class Permutation can be written as a single generator function. There's also a straight-forward implementation of a permutation generator (in C++ as std::next_permutation, does require strict weak ordering and starting from sorted data if you want all permutations), so that's perhaps not a convincing example. –  Oct 06 '10 at 17:03
  • The most amazing thing about C#'s `yield` statement is not the implementation, but the fact that you can step through it in the VS debugger. – Dour High Arch Oct 06 '10 at 18:17
  • @Roger Pate: I didn't use Duff's device in the original version, because some people may freak out looking at a jump inside a switch. As for the STL permutation, the last phrase from my blog was: "The code above doesn't use any kind of library... It simply uses language features to implement what we need. Now try writing the same functionality in plain C++, without using any library (STL permutations or otherwise) ...". So yes, there is a solution in the STL library for the specific problem, but yield allows you to solve ANY such kind of problem, using a language keyword, a generic tool. – ttsiodras Oct 07 '10 at 13:39
  • @ttsiodras: I mention next_permutation because it very simply does the job without a generator. Therefore, permutations is perhaps not a convincing example of why generators are useful. Has nothing to do with it being in a library, and I can write next_permutation in less than ten lines of readable code without using any library. –  Oct 07 '10 at 13:43
  • @Roger Pate: Not in as clear a manner as "yield" allows, not even close. The whole point of the post is that yield allows optimal, clarity-wise, code, with minimal effort. – ttsiodras Oct 07 '10 at 14:07
  • 2
    "Optimal clarity" is subjective. I can agree generators are nice and wish that C++ had them, while still having an opinion on whether a particular example justifying their use is convincing or not. :) –  Oct 07 '10 at 14:39
  • @ttsiodras: I have to agree with @Roger Pate on this, generators are nice, but I never "needed" them until encountering Python. They're a nice extra, but the problem with adding more features is less people know about them. Less is more and all that. That being said, I'd still like to see generators in C++, everything else has been smushed into the language, may as well keep going. – Matt Joiner Oct 07 '10 at 15:22
  • @ttsiodras: You have caller and callee backwards (which I had fixed). The caller is the user of the generator, the callee is the generator code itself. –  Oct 07 '10 at 15:39
  • @Roger: I did the same fix a bit earlier - our commits probably merged or something :-) – ttsiodras Oct 07 '10 at 16:32
  • @Matt: "less is more" definitely doesn't fit this discussion/rant: lambdas were added, via automatic class generation - so I don't see any reason to "discriminate" against "yield". The executive summary, as I get it, is: "we go where the committee majority wants to go", which is fine, democratic, and all that - and has some major drawbacks, like lambdas coming into C++ last (Python, Perl, C#, Haskell, Ocaml, etc have them for a decade now) and "yield" and other things are still in the future (if ever). – ttsiodras Oct 07 '10 at 16:39
  • @ttsiodras: My less is more comment was a general preference. It's obviously not the case in C++, and as I vaguely put it, it's too late for C++ anyway. I'm saying that I'm not against the addition of yield, the language is already a mess. I honestly believe that adding more crap to C++ will just make it more appealing to most people, it's already lost the interest of the parsimonious language fans. I should add that lambda's seem more convenient than yield. – Matt Joiner Oct 08 '10 at 08:27
  • porpose it for the next version! – dynamic May 26 '13 at 11:45
  • Just wanted to point out that the currently selected answer is no longer valid. The feature was proposed, and the feature is in the current compilers (clang and VC14) as a technical specification. Coroutine support is slated for after c++17 – Atif Aug 16 '16 at 16:00

8 Answers8

22

I can't say why they didn't add something like this, but in the case of lambdas, they weren't just added to the language either.

They started life as a library implementation in Boost, which proved that

  • lambdas are widely useful: a lot of people will use them when they're available, and that
  • a library implementation in C++03 suffers a number of shortcomings.

Based on this, the committee decided to adopt some kind of lambdas in C++0x, and I believe they initially experimented with adding more general language features to allow a better library implementation than Boost has.

And eventually, they made it a core language feature, because they had no other choice: because it wasn't possible to make a good enough library implementation.

New core language features aren't simply added to the language because they seem like a good idea. The committee is very reluctant to add them, and the feature in question really needs to prove itself. It must be shown that the feature is:

  • possible to implement in the compiler,
  • going to solve a real need, and
  • that a library implementation wouldn't be good enough.

In the case if a yield keyword, we know that the first point can be solved. As you've shown, it is a fairly simple transformation that can be done mechanically.

The second point is tricky. How much of a need for this is there? How widely used are the library implementations that exist? How many people have asked for this, or submitted proposals for it?

The last point seems to pass too. At least in C++03, a library implementation suffers some flaws, as you pointed out, which could justify a core language implementation. Could a better library implementation be made in C++0x though?

So I suspect the main problem is really a lack of interest. C++ is already a huge language, and no one wants it to grow bigger unless the features being added are really worth it. I suspect that this just isn't useful enough.

jalf
  • 243,077
  • 51
  • 345
  • 550
  • Language extensions can work without too much pain. In the Haskell compiler GHC, there is an incredible number of language extensions, and they work well because they are defined as transformations on a theoretic, simple, intermediate language. That means that the semantics are straight forward to reason about, although there can be lots of bells and whistles regarding syntax. – user239558 Mar 14 '13 at 08:23
  • @user239558: of course they can. But that doesn't really solve the problems I outlined – jalf Mar 14 '13 at 09:42
8

Adding a keyword is always tricky, because it invalidates previously valid code. You try to avoid that in a language with a code base as large as C++.

The evolution of C++ is a public process. If you feel yield should be in there, formulate an appropriate request to the C++ standard committee.

You will get your answer, directly from the people who made the decision.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • 1
    This is very believable. The ECMAScript committee went out of their way not to introduce a new keyword. EX: `Object.defineProperty` and `"use strict";` – ChaosPandion Oct 05 '10 at 14:19
  • 1
    Writing the proposal would be great, but it's too late to get such a feature into 0x. –  Oct 06 '10 at 17:05
  • This could have been in added without a keyword - as a library function. So -1. – einpoklum Aug 22 '17 at 21:49
7

So it looks like it didn't make it into C++11, or C++14, but might be on its way to C++17. Take a look at the lecture C++ Coroutines, a negative overhead abstraction from CppCon2015 and the paper here.

To summarize, they are working to extend c++ functions to have yield and await as features of functions. Looks like they have an initial implementation in Visual Studio 2015, not sure if clang has an implementation yet. Also it seems their may be some issues with using yield and await as the keywords.

The presentation is interesting because he speaks about how much it simplified networking code, where you are waiting for data to come in to continue the sequence of processing. Surprisingly, it looks like using these new coroutines results in faster/less code than what one would do today. It's a great presentation.

The resumable functions proposal for C++ can be found here.

Atif
  • 1,438
  • 16
  • 25
  • 1
    See also http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0099r0.pdf and http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0114r0.pdf and http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0158r0.html – Jonathan Wakely Feb 05 '16 at 14:17
7

They did it for lambdas, why didn't they consider it for supporting yield, too?

Check the papers. Did anyone propose it?

...I can only guess that they consider macro-based implementations to be an adequate substitute.

Not necessarily. I'm sure they know such macro solutions exist, but replacing them isn't enough motivation, on its own, to get new features passed.


Even though there are various issues around a new keyword, those could be overcome with new syntax, such as was done for lambdas and using auto as a function return type.

Radically new features need strong drivers (i.e. people) to fully analyze and push features through the committee, as they will always have plenty of people skeptical of a radical change. So even absent what you would view as a strong technical reason against a yield construct, there may still not have been enough support.

But fundamentally, the C++ standard library has embraced a different concept of iterators than you'd see with yield. Compare to Python's iterators, which only require two operations:

  1. an_iter.next() returns the next item or raises StopIteration (next() builtin included in 2.6 instead of using a method)
  2. iter(an_iter) returns an_iter (so you can treat iterables and iterators identically in functions)

C++'s iterators are used in pairs (which must be the same type), are divided into categories, it would be a semantic shift to transition into something more amenable to a yield construct, and that shift wouldn't fit well with concepts (which has since been dropped, but that came relatively late). For example, see the rationale for (justifiably, if disappointingly) rejecting my comment on changing range-based for loops to a form that would make writing this different form of iterator much easier.

To concretely clarify what I mean about different iterator forms: your generated code example needs another type to be the iterator type plus associated machinery for getting and maintaining those iterators. Not that it couldn't be handled, but it's not as simple as you may at first imagine. The real complexity is the "simple transformation" respecting exceptions for "local" variables (including during construction), controlling lifetime of "local" variables in local scopes within the generator (most would need to be saved across calls), and so forth.

  • 5
    Addressing: "Check the papers. Did anyone propose it?" - Well, if no-one in the C++ commitee realizes the benefits of coroutines/yield (in e.g. optimal async work in heavy-duty socket servers, LINQ-style queries, etc), it is no wonder that other languages are taking hold, dooming C++ to gradual extinction. As for iterators, they are not necessarily part of this discussion - yield stands on its own just fine without iterators, as a different way to return results. I am out of comment space - and prety much convinced that even obvious things, like yield, are "out-of-scope" for commitee-thinking. – ttsiodras Oct 07 '10 at 13:53
  • 4
    @ttsiodras: Look at those papers sometime, and the things that go into them. Not much is out of scope for committee thinking. If you were to join the Committee and push for something like `yield` and work hard to make it work in the language and listen to other people's objections, it would be in the next standard. That's just like everybody else's pet feature that didn't get included. – David Thornley Oct 07 '10 at 14:26
  • 3
    @ttsiodras: Iterators are a central feature of the standard library, but are treated as a feature of the language core (e.g. look at the 0x range-based for-loop; also why they were designed as a superset of pointers). I think any proposal of generators/yield that wasn't compatible with C++'s concept of iterators would be asked to be revised to make it so. –  Oct 07 '10 at 14:31
  • @ttsiodras: There bigger problems plaguing C++ than lack of features. Emphasis on gradual, the only language that will die slower than C++, is C. – Matt Joiner Oct 07 '10 at 15:26
  • @David: All due respect, but "yield" is *not* my pet feature. Scheme, Perl, Python, C#, Ruby - shall I go on? Unless you consider "lambdas" and functional programming pet features, too. – ttsiodras Oct 07 '10 at 16:43
  • @Matt: You are probably right - hence this rant. I will be waiting for yield for another decade :-( (just as I have been waiting for lambdas...) – ttsiodras Oct 07 '10 at 16:44
  • 2
    @ttsiodras: I've apparently been unclear here: "pet feature" doesn't refer to something useless, or something you created, but a feature you really want in a programming language. For example, in a COBOL discussion my pet feature would be user-defined functions that return a value, which is clearly useful, clearly not my idea, and present in more programming languages than `yield`. Different people have different pet features, most of which would be useful. Since they're all work to get right, and nobody wants to add too many, some get left out. – David Thornley Oct 07 '10 at 17:01
  • @ttsiodras: why wait? Submit a proposal for it to be added. ;) – jalf Dec 13 '10 at 17:24
  • The proposal does exist, and will likely make it in to c++17. Look up resumable functions: http://open-std.org/JTC1/SC22/WG21/docs/papers/2014/n4286.pdf – Atif Feb 29 '16 at 03:46
2

In general, you can track what's going on by the committee papers, although it's better for keeping track rather than looking up a specific issue.

One thing to remember about the C++ committee is that it is a volunteer committee, and can't accomplish everything it wants to. For example, there was no hash-type map in the original standard, because they couldn't manage to make it in time. It could be that there was nobody on the committee who cared enough about yield and what it does to make sure the work got done.

The best way to find out would be to ask an active committee member.

David Thornley
  • 56,304
  • 9
  • 91
  • 158
  • 1
    I am sort of prejudiced against committees - it is my "feeling" that languages designed by committees are usually... problematic. You would be right in arguing that I should therefore leave C++ and C++0x behind me, and focus on Python and Perl and other "benevolent dictator" languages... but I can't. What really bothered me and made me write this question to SO, was that they *did* the work for lambdas, and didn't take one small step to do it for co-routines, too.... (sigh)... C++, always some steps behind others... (with the exception of templates) – ttsiodras Oct 05 '10 at 14:47
  • @ttsiodras: My 2 languages are Python and C. C is just the bare minimum, you aren't sucked into any stupidness, and it's blazing fast and can do anything. The other is Python, for the reasons you've given. One guy has his finger on the pulse, and I like the way he thinks. C++ is a mess. – Matt Joiner Oct 05 '10 at 16:08
  • 1
    @ttsiodras: If you're truly prejudiced against committees, you're free to fork off from C++ and make your own language. If you're *very* good and provide something useful, you'll attract other people who will use the language you've created. After a while, you might decide that some of these other people have good ideas too for how the language you've created should evolve; invite some of them to help you, and you've got a committee. Aaaargh! :-) Committees aren't anything to be afraid of *per se* but not all are equal; some really suck. – Donal Fellows Oct 09 '10 at 19:55
  • 2
    @ttsiodras: I think C++ is proof that design by committee *can* work. They've done a good job so far, and while yes, they've screwed up occasionally, I think the same can be said for any "benevolent dictator". If anything, I'd argue that most of the cases where they screwed up were when they tried to act like benevolent dictators. – jalf Dec 13 '10 at 17:27
  • 1
    @jalf: My pettest peeve is `vector`, and of course there's `export`, which both were cases of the Committee pushing something and acting as benevolent dictator. – David Thornley Dec 13 '10 at 18:02
  • exactly. (and yeah, I had those two "features" in mind as well ;)) – jalf Dec 13 '10 at 18:04
2

Well, for such a trivial example as that, the only problem I see is that std::type_info::hash_code() is not specified constexpr. I believe a conforming implementation could still make it so and support this. Anyway the real problem is obtaining unique identifiers, so there might be another solution. (Obviously I borrowed your "master switch" construct, thanks.)

#define YIELD(X) do { \
    constexpr size_t local_state = typeid([](){}).hash_code(); \
    return (X); state = local_state; case local_state: ; } \
while (0)

Usage:

struct GeneratedFibonacci {
    size_t state;
    int a, b;

    GeneratedFibonacci() : state (0), a (0), b (1) {}

    int operator()() {
        switch (state) {
        case 0:
            while (true) {
                YIELD( a );
                int c = a + b;
                a = b;
                b = c;
            }
        }
    }
}

Hmm, they would also need to guarantee that the hash isn't 0. No biggie there either. And a DONE macro is easy to implement.


The real problem is what happens when you return from a scope with local objects. There is no hope of saving off a stack frame in a C-based language. The solution is to use a real coroutine, and C++0x does directly address that with threads and futures.

Consider this generator/coroutine:

void ReadWords() {
    ifstream f( "input.txt" );

    while ( f ) {
        string s;
        f >> s;
        yield s;
    }
}

If a similar trick is used for yield, f is destroyed at the first yield, and it's illegal to continue the loop after it, because you can't goto or switch past a non-POD object definition.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    Thanks for answering, but you are not telling me anything I didn't already know - yes, we can emulate some aspects of yield with macros, and Simon (the linked article in my original text) did this years ago. The problem is that this is not enough, as both I and you pointed out. And since C++0x added support for lambdas by adding automatic class generation to the compiler, and "yield" would require exactly the same thing, I just lament its absence. That's all. – ttsiodras Oct 10 '10 at 09:03
  • 1
    @ttsiodras: No, "yield" requires saving and restoring the stack frame, or equivalently a coroutine. In C languages that is equivalent to threading. In C++ if you want a generator object with that syntax, you must spawn a thread and synchronize it. And C++0x did add native support for that. – Potatoswatter Oct 10 '10 at 12:55
  • Hmm, just got one upvote, 2 years later! At a glance, `typeid([](){})` is not a valid way to get a compile-time random number. Perhaps my compile-time counters answer would work, though. – Potatoswatter Sep 17 '12 at 02:24
1

there have been several implementation of coroutines as user-space libraries. However, and here is the deal, those implementations rely on non-standard details. For example, nowhere on the c++ standard is specified how stack frames are kept. Most implementations just copy the stack because that is how most c++ implementations work

regarding standards, c++ could have helped coroutine support by improving the specification of stack frames.

Actually 'adding' it to the language doesn't sound a good idea to me, because that would stick you with a 'good enough' implementation for most cases that is entirely compiler-dependent. For the cases where using a coroutine matters, this is not acceptable anyways

lurscher
  • 25,930
  • 29
  • 122
  • 185
0

agree with @Potatoswatter first.

To support coroutine is not the same thing as support for lambdas and not that simple transformation like played with Duff's device.

You need full asymmetric coroutines (stackful) to work like generators in Python. The implementation of Simon Tatham's and Chris' are both stackless while Boost.Coroutine is a stackfull one though it's heavy.

Unfortunately, C++11 still do not have yield for coroutines yet, maybe C++1y ;)

PS: If you really like Python-style generators, have a look at this.

kelviN
  • 975
  • 6
  • 16