为什么没有将产量添加到 C++0x 中?
编辑,在我提出这个问题 11 年后:我觉得我的提问是正确的! C++20 终于做了一些足够接近的事情。
原来的问题如下。
--
我已经在许多 Python 程序中使用了 Yield,它在很多情况下确实使代码变得清晰。我在博客上介绍了它,它是我网站的热门页面之一。
C#还提供了yield——它是通过调用方的状态保持来实现的,通过自动生成的类来完成,该类可以保持状态、函数的局部变量等。
我目前正在阅读有关C++0x及其附加内容的内容;在阅读 C++0x 中 lambda 的实现时,我发现它也是通过自动生成的类完成的,配备有存储 lambda 代码的operator()。我脑海中自然浮现出一个问题:他们为 lambda 这样做了,为什么他们不考虑用它来支持“yield”呢?
当然他们可以看到协同例程的价值......所以我只能猜测他们认为基于宏的实现(例如 Simon Tatham 的)作为适当的替代品。然而,由于多种原因,它们并非如此:被调用者保持状态、不可重入、基于宏(仅此一点就足够了)等。
编辑: yield
不不依赖于垃圾收集、线程或纤程。你可以看Simon的文章,看到我说的是编译器做一个简单的转换,比如:
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?不,线程?不,纤维?不,简单的改造?可以说,是的。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
检查论文。有人提议吗?
未必。我确信他们知道存在这样的宏观解决方案,但替换它们本身并不足以推动新功能的通过。
尽管围绕新关键字存在各种问题,但这些问题可以通过新语法来克服,例如对 lambda 进行的操作以及使用 auto 作为函数返回类型。
彻底的新功能需要强大的驱动力(即人)来充分分析并通过委员会推动功能,因为他们总是会有很多人对彻底的改变持怀疑态度。因此,即使没有您认为反对收益率结构的强有力的技术原因,可能仍然没有足够的支持。
但从根本上来说,C++ 标准库采用了与您在yield 中看到的不同的迭代器概念。与 Python 的迭代器相比,它只需要两个操作:
C++ 的迭代器成对使用(必须是相同的类型),被分为不同的类别,这将是一种语义上的转变,转换成更适合yield 结构的东西,而这种转变不太适合与概念(此后已被放弃,但出现相对较晚)。例如,请参阅基本原理 for (合理地,如果令人失望地)拒绝我关于将基于范围的 for 循环更改为一种形式的评论,这种形式将使编写这种不同形式的迭代器变得更加容易。
为了具体澄清我对不同迭代器形式的含义:您生成的代码示例需要另一个类型作为迭代器类型以及用于获取和维护这些迭代器的关联机制。并不是说它无法处理,而是它并不像您一开始想象的那么简单。真正的复杂性是“简单转换”,尊重“局部”变量的异常(包括在构造期间),控制生成器内局部范围内“局部”变量的生命周期(大多数需要在调用之间保存),等等。
Check the papers. Did anyone propose it?
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:
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.
所以看起来它没有进入 C++11 或 C++14,但可能正在进入 C++17。看看来自 CppCon2015 的讲座C++ Coroutines, a Negative Overhead Abstract和论文此处。
总而言之,他们正在努力扩展 C++ 函数,以将yield 和await 作为函数的功能。看起来他们在 Visual Studio 2015 中有一个初步实现,不确定 clang 是否有实现。此外,使用yield 和await 作为关键字似乎可能存在一些问题。
该演示很有趣,因为他谈到了它如何简化了网络代码,您正在等待数据进入以继续处理序列。令人惊讶的是,看起来使用这些新的协程会产生比现在更快/更少的代码。这是一个很棒的演示。
C++ 的可恢复函数提案可以在此处找到。
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.
一般来说,您可以跟踪委员会论文,尽管它更适合跟踪而不是查找特定问题。
关于 C++ 委员会需要记住的一件事是,它是一个志愿者委员会,无法完成它想要完成的一切。例如,原来的标准中没有哈希类型的映射,因为他们没能及时做出来。可能是委员会中没有人足够关心
产量
以及它如何确保工作完成。找出答案的最佳方法是询问活跃的委员会成员。
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.
好吧,对于这样一个简单的例子,我看到的唯一问题是
std::type_info::hash_code()
没有指定constexpr
。我相信一致的实现仍然可以做到这一点并支持这一点。无论如何,真正的问题是获取唯一标识符,因此可能还有另一种解决方案。 (显然我借用了你的“主开关”构造,谢谢。)用法:
嗯,他们还需要保证哈希值不为 0。这也没什么大不了的。
DONE
宏很容易实现。真正的问题是当您从具有本地对象的范围返回时会发生什么。在基于 C 的语言中不可能保存堆栈帧。解决方案是使用真正的协程,C++0x 确实通过线程和 future 直接解决了这个问题。
考虑这个生成器/协程:
如果对
yield
使用类似的技巧,f
将在第一个yield
处被销毁,并且继续下去是非法的在它后面循环,因为您无法goto
或switch
过去非POD对象定义。Well, for such a trivial example as that, the only problem I see is that
std::type_info::hash_code()
is not specifiedconstexpr
. 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.)Usage:
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:
If a similar trick is used for
yield
,f
is destroyed at the firstyield
, and it's illegal to continue the loop after it, because you can'tgoto
orswitch
past a non-POD object definition.协程已经有多种作为用户空间库的实现。然而,事实是,这些实现依赖于非标准细节。例如,c++ 标准中没有指定如何保存堆栈帧。大多数实现只是复制堆栈,因为这就是大多数 C++ 实现
在标准方面的工作方式,C++ 可以通过改进堆栈帧的规范来帮助协程支持。
实际上,将其“添加”到语言中对我来说听起来并不是一个好主意,因为在大多数情况下,这会让您坚持“足够好”的实现,而这完全依赖于编译器。对于使用协程很重要的情况,这是不可接受的
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
首先同意@Potatoswatter 的观点。
支持协程与支持 lambda 不同,也不是像 Duff 设备那样的简单转换。
您需要完整的非对称协程(堆栈)才能工作就像 Python 中的生成器一样。 Simon Tatham 的 和 Chris 的 都是无堆栈的,而 Boost.Coroutine 是一个堆栈式的,尽管它很重。
不幸的是,C++11 仍然没有协程的
yield
,也许是 C++1y ;)PS:如果你真的喜欢 Python 风格的生成器,请看看 这个。
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.
我不能说为什么他们没有添加这样的东西,但就 lambda 而言,它们也不是只是添加到语言中的。
它们最初是 Boost 中的库实现,这证明了
基于此,委员会决定在 C++0x 中采用某种 lambda,我相信他们最初尝试添加更多通用语言功能以允许更好库实现比 Boost 具有。
最终,他们将其作为核心语言功能,因为他们别无选择:因为不可能创建一个足够好的库实现。
新的核心语言功能并不是简单地添加到语言中,因为它们看起来是个好主意。委员会非常不愿意添加它们,而相关功能确实需要证明自己。必须证明该功能是:
如果有一个
yield
关键字,我们知道第一点就可以解决。正如您所展示的,这是一个相当简单的转换,可以机械地完成。第二点很棘手。对此的需求有多大?现有的库实现的使用范围有多大?有多少人提出过这个要求,或者为此提交了提案?
最后一点似乎也通过了。正如您所指出的,至少在 C++03 中,库实现存在一些缺陷,这可以证明核心语言实现的合理性。那么 C++0x 中是否可以实现更好的库实现呢?
所以我怀疑主要问题确实是缺乏兴趣。 C++ 已经是一门庞大的语言,没有人希望它变得更大,除非添加的功能真正值得。我怀疑这还不够有用。
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
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:
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.
添加关键字总是很棘手,因为它会使以前有效的代码失效。在代码库像 C++ 这样大的语言中,您会尝试避免这种情况。
C++的演变是一个公共过程。如果您认为
yield
应该包含在其中,请向 C++ 标准委员会提出适当的请求。您将直接从做出决定的人那里得到答案。
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.