30

编辑,在我问这个问题 11 年后:我觉得问这个问题是正确的!C++20 终于做了足够接近的事情

原始问题如下。

--

我一直在我的许多 Python 程序中使用 yield,它确实在许多情况下清除了代码。我写了一篇关于它的博客,它是我网站的热门页面之一。

C#还提供yield——它是通过调用方的状态保持来实现的,通过一个自动生成的类来保持状态、函数的局部变量等。

我目前正在阅读有关 C++0x 及其添加的内容;在阅读 C++0x 中 lambdas 的实现时,我发现它也是通过自动生成的类完成的,配备了 operator() 存储 lambda 代码。我脑海中自然形成了一个问题:他们是为 lambdas 做的,为什么他们不考虑支持“收益”呢?

当然,他们可以看到协同程序的价值......所以我只能猜测他们认为基于宏的实现(例如Simon Tatham 的)是一个足够的替代品。然而,它们不是,有很多原因:被调用者保持状态、不可重入、基于宏(仅此一点就足够了)等。

编辑: 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;
            }
        }
    }
}

垃圾收集?不,线程?不,纤维?不,简单的转变?可以说,是的。

4

8 回答 8

22

我不能说他们为什么不添加这样的东西,但在 lambdas 的情况下,它们也不只是被添加到语言中。

他们以 Boost 中的库实现开始生活,这证明了

  • lambda 非常有用:很多人会在可用时使用它们,而且
  • C++03 中的库实现存在许多缺点。

基于此,委员会决定在 C++0x 中采用某种lambda,我相信他们最初尝试添加更多通用语言特性,以实现比 Boost更好的库实现。

最终,他们将其作为核心语言特性,因为他们别无选择:因为不可能做出足够好的库实现。

新的核心语言特性并不是简单地添加到语言中,因为它们看起来是个好主意。委员会非常不愿意添加它们,而有问题的功能确实需要证明自己。必须证明该特征是:

  • 可以在编译器中实现,
  • 去解决一个真正的需求,并且
  • 库实现还不够好。

如果是yield关键字的话,我们知道第一点就可以解决了。正如您所展示的,这是一个相当简单的转换,可以机械地完成。

第二点很棘手。这有多大的需求?现有的库实现的使用范围有多广?有多少人为此提出过要求,或为此提交过提案?

最后一点似乎也过去了。正如您所指出的,至少在 C++03 中,库实现存在一些缺陷,这可以证明核心语言实现是合理的。可以在 C++0x 中实现更好的库吗?

所以我怀疑主要问题实际上是缺乏兴趣。C++ 已经是一门巨大的语言,没有人希望它变得更大,除非添加的特性真的值得。我怀疑这还不够有用。

于 2010-12-13T17:43:18.710 回答
8

添加关键字总是很棘手,因为它会使以前有效的代码无效。您尝试在具有与 C++ 一样大的代码库的语言中避免这种情况。

C++ 的演变是一个公共过程。如果您觉得yield应该参与其中,请向 C++ 标准委员会提出适当的要求。

您将直接从做出决定的人那里得到答案。

于 2010-10-05T14:16:44.870 回答
7

他们是为 lambdas 做的,为什么他们不考虑它来支持产量呢?

检查文件。有人提议吗?

...我只能猜测他们认为基于宏的实现是一个足够的替代品。

不必要。我确信他们知道存在这样的宏观解决方案,但更换它们本身并不足以推动新功能的通过。


尽管围绕 new 关键字存在各种问题,但可以通过新语法来克服这些问题,例如为 lambdas 所做的以及使用 auto 作为函数返回类型。

根本性的新功能需要强大的驱动力(即人员)来全面分析并通过委员会推动功能,因为他们总是会有很多人对根本性的改变持怀疑态度。因此,即使没有你认为反对收益率结构的强有力的技术理由,也可能仍然没有足够的支持。

但从根本上说,C++ 标准库包含的迭代器概念与您在 yield 中看到的不同。与 Python 的迭代器相比,它只需要两个操作:

  1. an_iter.next() 返回下一项或引发 StopIteration (next() 内置在 2.6 中,而不是使用方法)
  2. iter(an_iter) 返回 an_iter (因此您可以在函数中同等对待迭代器和迭代器)

C++ 的迭代器成对使用(必须是相同的类型),被划分为类别,转换为更适合 yield 构造的东西将是语义上的转变,并且这种转变与概念不太吻合(它有自从被删除,但来得相对较晚)。例如,请参阅拒绝我关于将基于范围的 for 循环更改为一种可以使编写这种不同形式的迭代器更容易的形式的评论的理由(这是有道理的,如果令人失望的话)。

具体阐明我对不同迭代器形式的含义:您生成的代码示例需要另一种类型作为迭代器类型以及用于获取和维护这些迭代器的相关机制。不是说不能处理,而是没有你一开始想象的那么简单。真正的复杂性是“简单转换”,尊重“局部”变量的异常(包括在构造期间),控制生成器内局部范围内的“局部”变量的生命周期(大多数需要跨调用保存)等等。

于 2010-10-06T13:03:26.460 回答
7

所以看起来它没有进入 C++11 或 C++14,但可能正在进入 C++17。看看讲座C++ Coroutines,来自 CppCon2015的负开销抽象和这里的论文。

总而言之,他们正在努力扩展 c++ 函数,使其具有 yield 和 await 作为函数的特性。看起来他们在 Visual Studio 2015 中有一个初始实现,不确定 clang 是否有一个实现。此外,使用 yield 和 await 作为关键字似乎存在一些问题。

该演示文稿很有趣,因为他谈到了它在多大程度上简化了网络代码,您正在等待数据进入以继续处理序列。令人惊讶的是,使用这些新的协程看起来比现在的代码更快/更少。这是一个很棒的介绍。

可以在此处找到 C++ 的可恢复函数提案。

于 2016-02-05T13:46:26.947 回答
2

一般来说,您可以通过委员会文件跟踪正在发生的事情,尽管最好跟踪而不是查找特定问题。

关于 C++ 委员会要记住的一件事是,它是一个志愿委员会,不能完成它想要做的所有事情。例如,原始标准中没有哈希类型的映射,因为他们无法及时制作。可能是委员会中没有人足够关心yield它为确保工作完成所做的工作。

找出答案的最好方法是询问活跃的委员会成员。

于 2010-10-05T14:34:53.957 回答
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;
    }
}

如果对 , 使用了类似的技巧yieldf则在第一个被破坏yield,并且在它之后继续循环是非法的,因为您不能gotoswitch超过非 POD 对象定义。

于 2010-10-09T19:47:08.220 回答
1

已经有几种协同程序作为用户空间库的实现。然而,这就是交易,这些实现依赖于非标准的细节。例如,c++ 标准中没有任何地方指定如何保​​存堆栈帧。大多数实现只是复制堆栈,因为这是大多数 C++ 实现的工作方式

关于标准,c++ 可以通过改进堆栈帧的规范来帮助协程支持。

实际上,将它“添加”到语言中对我来说听起来不是一个好主意,因为对于大多数完全依赖于编译器的情况,这会让你得到一个“足够好”的实现。对于使用协程很重要的情况,无论如何这是不可接受的

于 2010-12-13T16:31:40.000 回答
0

首先同意@Potatoswatter。

支持协程与支持 lambda 不是一回事,也不是像使用 Duff 的设备那样简单的转换。

你需要完整的非对称协程(stackful)才能像 Python 中的生成器一样工作。Simon TathamChris 的实现都是无堆栈的,而Boost.Coroutine是一个堆栈式的,尽管它很重。

不幸的是,C++11 仍然没有yield协程,也许是 C++1y ;)

PS:如果你真的喜欢 Python 风格的生成器,看看这个.

于 2013-03-26T07:56:08.347 回答