【问题标题】:What's the special value of `co_yield` in contrast to a simple stateful lambda in C++20?与 C++20 中的简单有状态 lambda 相比,`co_yield` 的特殊值是什么?
【发布时间】:2021-12-30 18:53:47
【问题描述】:

来自著名的 C++ 协程库(在源文件generator.hpp 中搜索“Don't allow any use of co_await inside the generator coroutine.”)和我自己的实验,我知道协程使用co_yield不能同时使用co_await

既然使用co_yield 的生成器必须是同步的,那么使用co_yield 相对于简单的有状态 lambda 有什么优势?

例如:

#include <iostream>

generator<int> g()
{
    for (auto i = 0; i < 9; ++i)
    {
        co_yield i;
    }
}

int main()
{
    auto fn_gen = [i = 0] mutable { return i++; };

    // Lambda way
    for (auto i = 0; i < 9; ++i)
    {
        std::cout << fn_gen() << std::endl;
    }

    // co_yield way
    for (auto i : g())
    {
        std::cout << i << std::endl;
    }
}

co_yield 与 C++20 中的简单有状态 lambda 相比有何特殊价值?

请查看更新后的 MWEhttps://godbolt.org/z/x1Yoen7Ys

在更新后的示例中,当在同一个协程中使用co_awaitco_yield 时,输出完全出乎意料。

【问题讨论】:

  • 你完全可以在同样使用co_yield的函数中使用co_await。甚至还有legitimate use cases for it
  • 简短回答:视情况而定。对于一个简单的生成器,状态完整的 lambda 可能更可取。但是协程生成器在更复杂的情况下可能会更简单,因为状态不像计数器那么简单。例如,目录中文件的递归迭代器。
  • 我在 clang-13 上测试过,使用co_awaitco_yield 会导致意外的resume 订单。也许这是协程实现中的一个错误。
  • @xmllmx:您必须显示测试用例中使用的全部代码,包括协程机器类型。显然,cppcoro 的 generator 类型有一个明确的机制,可以将尝试通过软管连接到发电机内部的 co_await
  • 嗯,一方面,这两个示例并不等同。在很多方面。生成器为您提供从 0 到 8 的数字。lambda 为您提供从 0 到...的数字。您可以调用 g() 两次,第二次从 0 开始,您必须复制 @987654341 @ 在您完全调用它以获得该行为之前。等

标签: c++ c++20 coroutine language-design c++-coroutine


【解决方案1】:

对于内部状态和代码最少的普通生成器,一个小的仿函数或 lambda 就可以了。但是随着您的生成器代码变得更加复杂并且需要更多状态,它变得不那么精细了。您必须在仿函数类型或 lambda 说明符中添加更多成员。您在函数内部有越来越大的代码。等等。

在最极端的情况下,基于co_yield 的生成器可以向外界隐藏其实现细节的所有,只需将其定义放在 .cpp 文件中即可。有状态函子不能隐藏其内部状态,因为它的状态是外部世界必须看到的类型的成员。避免这种情况的唯一方法是通过类型擦除,例如使用std::function 之类的东西。在这一点上,您基本上只使用co_yield 没有任何收获。

另外,co_await 可以与co_yield 一起使用。 Cppcoro 的 generator 类型显式地处理它,但 cppcoro 不是 C++20。你可以编写任何你想要的生成器,那个生成器可以support uses of co_await for specific purposes

确实,您可以制作异步生成器,有时您可以立即生成一个值,有时您可以通过一些异步过程安排一个值的可用性。调用异步生成器的代码可以在其上co_await 以从中提取值,而不是将其视为仿函数或迭代器对。

【讨论】:

  • 请查看我在原始问题中更新的示例,说明在同一协程中同时使用 co_awaitco_yield 时的意外输出。
  • @xmllmx:你做错了。我在您的承诺类型中没有看到任何 await_transform 用法,在您的生成器界面中也没有看到任何尚未返回值的生成器的可供性。
  • await_transform 是可选的。如果没有co_await,则输出如预期。
  • @xmllmx:它是可选的,因为您可能不需要使用它来实现您想要实现的某些特定效果。老实说,我不知道你是否需要它。但我没有在您的承诺或未来类型中看到 anything 可以执行任何需要允许在您的生成器中使用 co_await 的操作。 Awaiter 在哪里安排恢复等待它的协程?调用生成器可能会或可能不会返回值,但您无法从接口代码中分辨出来。你必须正确使用协程机制才能得到你想要的效果。
  • 非常感谢 await_transform 的提示,我会重新考虑 MWE。
猜你喜欢
  • 2020-09-13
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 2013-06-16
  • 1970-01-01
  • 2020-10-28
  • 2022-01-14
  • 1970-01-01
相关资源
最近更新 更多