我在许多Python程序中都使用了yield,并且在很多情况下确实清除了代码。我blogged about it,这是我网站的热门网页之一。
C#还提供了收益 - 它通过调用者端的状态保持来实现,通过自动生成的类来完成,该类保持函数的状态,局部变量等。
我目前正在阅读有关C ++ 0x及其添加的内容;在阅读有关C ++ 0x中lambda的实现时,我发现它也是通过自动生成的类完成的,配备了存储lambda代码的operator()。我心中形成了一个自然的问题:他们是为lambdas做过的,他们为什么不考虑支持“收益”呢?
当然,他们可以看到协同例程的价值......所以我只能猜测他们认为基于宏的实现(例如Simon Tatham's)是一个充分的替代品。然而,它们不是出于多种原因:被呼叫者保持状态,非重入状态,基于宏观(仅此一点是足够的理由)等等。
编辑: yield
不依赖于垃圾收集,线程或光纤。您可以阅读Simon的文章,看看我在谈论编译器进行简单的转换,例如:
int fibonacci() {
int a = 0, b = 1;
while (true) {
yield a;
int c = a + b;
a = b;
b = c;
}
}
分为:
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;
}
}
}
}
垃圾收集?号码线程?不是纤维?不。简单转型?可以,是的。
答案 0 :(得分:22)
我不能说为什么他们没有添加这样的东西,但是在lambdas的情况下,他们不是只是添加到语言中。
他们在Boost中开始了作为库实现的生活,证明了
基于此,委员会决定在C ++ 0x中采用某种 lambdas,我相信他们最初尝试添加更多通用语言功能以允许更好库实现比Boost有。
最终,他们将其作为核心语言功能,因为他们别无选择:因为无法创建足够好的库实现。
新的核心语言功能并不是简单地添加到语言中,因为它们似乎是一个好主意。委员会非常不愿意添加它们,而真正的功能需要证明自己。必须显示该功能是:
如果是yield
关键字,我们知道第一点可以解决。如您所示,这是一个相当简单的转换,可以通过机械方式完成。
第二点很棘手。这个需要多少?存在的库实现有多广泛使用?有多少人要求这样做,或者为此提交了提案?
最后一点似乎也过去了。至少在C ++ 03中,库实现会遇到一些缺陷,正如您所指出的那样,可以证明核心语言实现的合理性。可以在C ++ 0x中实现更好的库实现吗?
所以我怀疑主要问题实际上是缺乏兴趣。 C ++已经是一种庞大的语言,没有人希望它变得更大,除非添加的功能真的值得。我怀疑这只是不够用。
答案 1 :(得分:8)
添加关键字总是很棘手,因为它使先前有效的代码无效。您尝试使用与C ++一样大的代码库的语言来避免这种情况。
C ++的发展是一个公共过程。如果您认为yield
应该在那里,请向C ++标准委员会提出适当的请求。
您将直接从做出决定的人那里得到答案。
答案 2 :(得分:7)
他们为lambdas做过,他们为什么不考虑支持收益呢?
检查papers。有人提议吗?
......我只能猜测他们认为基于宏的实现是一个充分的替代品。
不一定。我敢肯定他们知道存在这样的宏观解决方案,但是取而代之的并不足以让自己获得新功能。
即使围绕新关键字存在各种问题,也可以使用新语法克服这些问题,例如为lambdas做的并使用auto作为函数返回类型。
从根本上来说,新功能需要强大的驱动程序(即人员)来通过委员会来全面分析和推动功能,因为他们总是会有很多人对激进的变化持怀疑态度。因此,即使没有你认为对抗收益率结构的强大技术理由,也可能仍然没有足够的支持。
但从根本上说,C ++标准库已经采用了与使用yield看到的不同的迭代器概念。与Python的迭代器相比,它只需要两个操作:
C ++的迭代器成对使用(必须是同一类型),分为几类,它将是一种语义转换,可以转换为更适合于yield构造的东西,并且这种转换不适合概念(此后已被删除,但来得相对较晚)。例如,请参阅rationale(理所当然,如果令人失望的话)拒绝我对将基于范围的for循环更改为一种形式的评论,这种形式会使编写这种不同形式的迭代器变得更加容易。
具体说明我对不同迭代器形式的含义:生成的代码示例需要另一个类型作为迭代器类型以及用于获取和维护这些迭代器的相关机制。并不是说它无法处理,但它并不像你最初想象的那么简单。真正的复杂性是关于“局部”变量(包括构造期间)的异常的“简单转换”,控制生成器内的局部范围中的“本地”变量的生命周期(大多数需要跨调用保存),等等。
答案 3 :(得分:7)
所以看起来它没有进入C ++ 11或C ++ 14,但可能正在进入C ++ 17。看看CppCon2015的讲座C++ Coroutines, a negative overhead abstraction和论文here。
总而言之,他们正在努力扩展c ++函数以获得收益并等待函数的特征。看起来他们在Visual Studio 2015中有一个初始实现,不确定clang是否还有一个实现。此外,似乎它们可能是使用yield并等待作为关键字的一些问题。
该演示文稿非常有趣,因为他谈到了简化网络代码的程度,您在等待数据进入以继续处理的顺序。令人惊讶的是,看起来使用这些新的协同程序会产生比现在更快/更少的代码。这是一个很棒的演讲。
可以找到C ++的可恢复功能提议here。
答案 4 :(得分:2)
一般情况下,您可以跟踪committee papers发生的情况,但最好是跟踪而不是查找特定问题。
关于C ++委员会要记住的一点是,它是一个志愿者委员会,无法完成它想要的一切。例如,原始标准中没有哈希类型映射,因为它们无法及时完成。可能是委员会中没有人关心yield
及其如何确保工作完成。
最好的方法是找一位活跃的委员会成员。
答案 5 :(得分:2)
嗯,对于这样一个微不足道的例子,我看到的唯一问题是std::type_info::hash_code()
未指定constexpr
。我相信一致的实施仍然可以做到并支持这一点。无论如何,真正的问题是获得唯一标识符,因此可能存在另一种解决方案。 (显然我借用了你的“主开关”结构,谢谢。)
#define YIELD(X) do { \
constexpr size_t local_state = typeid([](){}).hash_code(); \
return (X); state = local_state; case local_state: ; } \
while (0)
用法:
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;
}
}
}
}
嗯,他们还需要保证散列不是0.也没有大问题。并且DONE
宏很容易实现。
真正的问题是从具有本地对象的作用域返回时会发生什么。没有希望以基于C语言保存堆栈帧。解决方案是使用真正的协同程序,C ++ 0x直接解决线程和期货的问题。
考虑这个生成器/协同程序:
void ReadWords() {
ifstream f( "input.txt" );
while ( f ) {
string s;
f >> s;
yield s;
}
}
如果对yield
使用类似的技巧,f
会在第一个yield
被销毁,并且继续循环之后是非法的,因为你不能{{1 }}或goto
超过非POD对象定义。
答案 6 :(得分:1)
已经有多个协程实现为用户空间库。然而,这是交易,这些实现依赖于非标准细节。例如,c ++标准中没有指定堆栈帧的保存方式。大多数实现只是复制堆栈,因为这是大多数c ++实现的工作方式
关于标准,c ++可以通过改进堆栈帧的规范来帮助协程支持。
实际上,将它添加到语言对我来说听起来并不是一个好主意,因为对于大多数完全依赖于编译器的情况,这会给你一个“足够好”的实现。对于使用协程事项的情况,无论如何这是不可接受的
答案 7 :(得分:0)
首先同意@Potatoswatter。
支持协程与支持lambdas不是一回事,而不是像Duff的设备那样简单转换。
你需要full asymmetric coroutines(stackful)才能像Python中的生成器一样工作。 Simon Tatham's和Chris'的实现都是无堆栈的,而Boost.Coroutine虽然很重,却是一个堆栈。
不幸的是,C ++ 11对于协同程序仍然没有yield
,可能是C ++ 1y;)
PS:如果您真的喜欢Python风格的生成器,请查看this。