c++20 coroutines 翻译

翻译 https://en.cppreference.com/w/cpp/language/coroutines


个人总结

翻译目的是为了自己熟悉过程中更好的关注当前部分细节;
直接看翻译好的有些地方翻译很差,容易出错,
而且一次看到太多信息容易忽略细节内容。

有机会尝试画一个流程图出来吧?

格式:
中文
english

疑问:
1、疑问:co_await 是在函数开始的地方等待,还是执行完在返回的地方等待,目前理解是开始的地方
2、疑问:这里的 generator 是什么用法,什么作用?
3、疑问:这里的 lazy 是什么用法,什么作用?
4、疑问:这是一个非拥有句柄,什么意思?
5、疑问:post-copy 什么意思?
6、疑问:协程支持多线程并行吗? 如果支持如何实现的?
7、疑问:这几种情况具体是什么情形
8、疑问:开始执行协程具体内容的时机:promise.initial_suspend() 后

c++20的协程要么好好看,要么就不要看了,简单看很容易搞不懂用法,这是一个编程框架,不是像vector那样一个边界明确和其他无关的类模版。我暂时也是理解内容,实际用还需再梳理一下才可以。好像不如有栈协程方便使用。


Coroutines (C++20)

一个 coroutine 是可以暂停和后续恢复执行的函数。Coroutines 无栈:暂停后回到调用者,上下文数据存储和栈无关。这就允许异步执行一系列操作(如没有明确回调函数的非阻塞IO),也支持没有严格顺序要求的延迟计算和其他用法。
A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller, and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously (e.g. to handle non-blocking I/O without explicit callbacks), and also supports algorithms on lazy-computed infinite sequences and other uses.

一个函数如果包含以下内容的一个就是 coroutine:
A function is a coroutine if its definition contains any of the following:

  • co_await - 暂停操作等待恢复
  • the co_await expression — to suspend execution until resumed
task<> tcp_echo_server()
{
    char data[1024];
    while (true)
    {
        std::size_t n = co_await socket.async_read_some(buffer(data));
        co_await async_write(socket, buffer(data, n));
    }
}

疑问:co_await 是在函数开始的地方等待,还是执行完在返回的地方等待,目前理解是开始的地方
  • co_yield — 暂停执行返回一个值
  • the co_yield expression — to suspend execution returning a value
generator<unsigned int> iota(unsigned int n = 0)
{
    while (true)
        co_yield n++;
}

疑问:这里的 generator 是什么用法,什么作用?
  • co_return — 完成计算过程,返回一个值
  • the co_return statement — to complete execution returning a value
lazy<int> f()
{
    co_return 7;
}

疑问:这里的 lazy 是什么用法,什么作用?

每个协程都必须有一个返回类型,满足一些要求,如下所述。
Every coroutine must have a return type that satisfies a number of requirements, noted below.

Restrictions 限制条件

协程不能使用可变参数、普通返回语句或占位符返回类型(auto或Concept)。
Coroutines cannot use variadic arguments, plain return statements, or placeholder return types (auto or Concept).

Consteval函数、constexpr函数、构造函数、析构函数和main函数不能是协程。
Consteval functions, constexpr functions, constructors, destructors, and the main function cannot be coroutines.

Execution

每个 coroutine 有如下假设
Each coroutine is associated with

  • promise对象,在协程内部操作。协程通过这个对象提交它的结果或异常。Promise对象与std:: Promise没有任何关系。
  • 协程句柄,从协程外部操作。这是一个非拥有句柄,用于恢复协程执行或销毁协程帧。
  • 协程状态,它是内部动态分配的存储空间(除非分配被优化),包含对象:
    • promise
    • parameters (都是拷贝值)
    • 当前挂起点的信息,这样resume就知道在哪里继续,而destroy就知道哪些局部变量在作用域中
    • 生存期跨越当前暂停点的局部变量和临时变量。
  • the promise object, manipulated from inside the coroutine. The coroutine submits its result or exception through this object. Promise objects are in no way related to std::promise.
  • the coroutine handle, manipulated from outside the coroutine. This is a non-owning handle used to resume execution of the coroutine or to destroy the coroutine frame.
  • the coroutine state, which is internal, dynamically-allocated storage (unless the allocation is optimized out), object that contains
    • the promise object
    • the parameters (all copied by value)
    • some representation of the current suspension point, so that a resume knows where to continue, and a destroy knows what local variables were in scope
    • local variables and temporaries whose lifetime spans the current suspension point.

疑问:这是一个非拥有句柄,什么意思?

当协程开始执行时,它会执行以下操作:
When a coroutine begins execution, it performs the following:

  • new 协程状态
  • 将所有函数参数复制到协程状态:按值参数被移动或复制,按引用参数保持引用(因此,如果协程在被引用对象的生命周期结束后恢复,则可能成为悬空(参见下面的示例)。
  • 调用promise对象的构造函数。如果promise类型有一个带有所有协程参数的构造函数,则该构造函数将被调用,使用 post-copy 的参数。否则调用默认构造函数。
  • 调用promise.get_return_object()并将结果保存在一个局部变量中。当协程第一次挂起时,调用的结果将返回给调用者。任何抛出的异常(包括此步骤)都会传播回调用者,而不是放在promise中。
  • 调用promise.initial_suspend(), co_awaits 等待结果。典型的Promise类型要么返回std::suspend_always(对于延迟启动的协程),要么返回std::suspend_never(对于提前启动的协程)。
  • 当co_await promise.initial_suspend()恢复时,开始执行协程的主体。
  • allocates the coroutine state object using operator new.
  • copies all function parameters to the coroutine state: by-value parameters are moved or copied, by-reference parameters remain references (thus, may become dangling, if the coroutine is resumed after the lifetime of referred object ends — see below for examples).
  • calls the constructor for the promise object. If the promise type has a constructor that takes all coroutine parameters, that constructor is called, with post-copy coroutine arguments. Otherwise the default constructor is called.
  • calls promise.get_return_object() and keeps the result in a local variable. The result of that call will be returned to the caller when the coroutine first suspends. Any exceptions thrown up to and including this step propagate back to the caller, not placed in the promise.
  • calls promise.initial_suspend() and co_awaits its result. Typical Promise types either return a std::suspend_always, for lazily-started coroutines, or std::suspend_never, for eagerly-started coroutines.
  • when co_await promise.initial_suspend() resumes, starts executing the body of the coroutine.

参数变为悬空的一些例子:
Some examples of a parameter becoming dangling:

#include <coroutine>
#include <iostream>

struct promise;

struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = ::promise;
};

struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};

struct S
{
    int i;
    coroutine f()
    {
        std::cout << i;
        co_return;
    }
};

void bad1()
{
    coroutine h = S{0}.f();
    // S{0} destroyed
    h.resume(); // resumed coroutine executes std::cout << i, uses S::i after free
    h.destroy();
}

coroutine bad2()
{
    S s{0};
    return s.f(); // returned coroutine can't be resumed without committing use after free
}

void bad3()
{
    coroutine h = [i = 0]() -> coroutine // a lambda that's also a coroutine
    {
        std::cout << i;
        co_return;
    }(); // immediately invoked
    // lambda destroyed
    h.resume(); // uses (anonymous lambda type)::i after free
    h.destroy();
}

void good()
{
    coroutine h = [](int i) -> coroutine // make i a coroutine parameter
    {
        std::cout << i;
        co_return;
    }(0);
    // lambda destroyed
    h.resume(); // no problem, i has been copied to the coroutine
                // frame as a by-value parameter
    h.destroy();
}

当协程到达暂停点时
When a coroutine reaches a suspension point

  • 返回值到caller或者resumer,如果需要会隐式转换为coroutine的返回类型。
  • the return object obtained earlier is returned to the caller/resumer, after implicit conversion to the return type of the coroutine, if necessary.

当协程到达co_return语句时,它执行以下操作:
When a coroutine reaches the co_return statement, it performs the following:

  • 调用 promise.return_void()
    • co_return;
    • co_return expr; where expr has type void
  • 或者调用 promise.return_value(expr) ,expr 为非void 类型
  • 按与创建顺序相反的顺序销毁所有具有自动存储期的变量。
  • 调用promise.final_suspend(), co_awaits 结果。
  • calls promise.return_void() for
    • co_return;
    • co_return expr; where expr has type void
  • or calls promise.return_value(expr) for co_return expr; where expr has non-void type
  • destroys all variables with automatic storage duration in reverse order they were created.
  • calls promise.final_suspend() and co_awaits the result.

coroutine 结束相当于co_return;如果没有 return_void 的定义是未知行为。
无论其返回类型如何,在函数体中没有定义关键字的函数都不是协程,如果返回类型不是(可能是cv限定的)void,则从end返回会导致未定义的行为。
Falling off the end of the coroutine is equivalent to co_return;, except that the behavior is undefined if no declarations of return_void can be found in the scope of Promise. A function with none of the defining keywords in its function body is not a coroutine, regardless of its return type, and falling off the end results in undefined behavior if the return type is not (possibly cv-qualified) void.

// assuming that task is some coroutine task type
task<void> f()
{
    // not a coroutine, undefined behavior
}

task<void> g()
{
    co_return;  // OK
}

task<void> h()
{
    co_await g();
    // OK, implicit co_return;
}

如果协程以未捕获异常结束,则执行以下操作:
If the coroutine ends with an uncaught exception, it performs the following:

  • 捕获异常并从捕获块中调用promise.unhandled_exception()
  • 调用promise.final_suspend()和co_await结果(例如恢复continuation或发布结果)。从这一点恢复协程是未定义的行为。
  • catches the exception and calls promise.unhandled_exception() from within the catch-block
  • calls promise.final_suspend() and co_awaits the result (e.g. to resume a continuation or publish a result). It's undefined behavior to resume a coroutine from this point.

When the coroutine state is destroyed either because it terminated via co_return or uncaught exception, or because it was destroyed via its handle, it does the following:
当协程状态被销毁时,要么因为它通过co_return或uncaught异常终止,要么因为它通过它的句柄被销毁,它会做以下操作:

  • 调用 promise 的析构函数
  • 调用参数的析构函数
  • 调用delete释放 state的资源
  • 回到caller/resumer.
  • calls the destructor of the promise object.
  • calls the destructors of the function parameter copies.
  • calls operator delete to free the memory used by the coroutine state.
  • transfers execution back to the caller/resumer.

Dynamic allocation 动态申请内存

通过new 获取动态内存存储state
Coroutine state is allocated dynamically via non-array operator new

如果Promise类型定义了类级别的替换,它将被使用,否则将使用全局new运算符。
If the Promise type defines a class-level replacement, it will be used, otherwise global operator new will be used.

如果Promise类型定义了一个放置形式的new运算符,它接受额外的参数,并且它们与参数列表匹配,其中第一个参数是请求的大小(类型为std::size_t),其余的是协程函数参数,这些参数将被传递给new运算符(这使得在协程中使用前置分配器约定成为可能)。
If the Promise type defines a placement form of operator new that takes additional parameters, and they match an argument list where the first argument is the size requested (of type std::size_t) and the rest are the coroutine function arguments, those arguments will be passed to operator new (this makes it possible to use leading-allocator-convention for coroutines).

对new运算符的调用可以被优化掉(即使使用了自定义分配器),需要满足如下条件
The call to operator new can be optimized out (even if custom allocator is used) if

  • 协程状态的生命周期严格嵌套在调用者的生命周期内,并且
  • 协程帧的大小在调用端是已知的。
  • The lifetime of the coroutine state is strictly nested within the lifetime of the caller, and
  • the size of coroutine frame is known at the call site.

在这种情况下,协程状态嵌入到调用者的栈帧(如果调用者是普通函数)或协程状态(如果调用者是协程)中。
In that case, coroutine state is embedded in the caller's stack frame (if the caller is an ordinary function) or coroutine state (if the caller is a coroutine).

如果分配失败,协程会抛出std::bad_alloc,除非Promise类型定义了成员函数Promise::get_return_object_on_allocation_failure()。如果定义了该成员函数,则分配使用new运算符的nothrow形式,并且在分配失败时,协程立即将从Promise::get_return_object_on_allocation_failure()获得的对象返回给调用者,例如:
If allocation fails, the coroutine throws std::bad_alloc, unless the Promise type defines the member function Promise::get_return_object_on_allocation_failure(). If that member function is defined, allocation uses the nothrow form of operator new and on allocation failure, the coroutine immediately returns the object obtained from Promise::get_return_object_on_allocation_failure() to the caller, e.g.:

struct Coroutine::promise_type
{
    /* ... */

    // ensure the use of non-throwing operator-new
    static Coroutine get_return_object_on_allocation_failure()
    {
        std::cerr << __func__ << '\n';
        throw std::bad_alloc(); // or, return Coroutine(nullptr);
    }

    // custom non-throwing overload of new
    void* operator new(std::size_t n) noexcept
    {
        if (void* mem = std::malloc(n))
            return mem;
        return nullptr; // allocation failure
    }
};

Promise

Promise的类型是由编译器使用std::coroutine_traits根据协程的返回类型确定的。
The Promise type is determined by the compiler from the return type of the coroutine using std::coroutine_traits.

Formally, let
正式的:

  • R和Args…分别表示协程的返回类型和参数类型列表,
  • 如果协程定义为非静态成员函数,则classst表示协程所属的类类型,
  • CV表示函数声明中声明的CV -限定,如果定义为非静态成员函数,
  • R and Args... denote the return type and parameter type list of a coroutine respectively,
  • ClassT denote the class type to which the coroutine belongs if it is defined as a non-static member function,
  • cv denote the cv-qualification declared in the function declaration if it is defined as a non-static member function,

它的Promise类型由以下因素决定:
its Promise type is determined by:

  • std:: coroutine_traits < R, Args…>::promise_type,如果协程没有定义为隐式对象成员函数,
  • std:: coroutine_traits < R, cvClassT&, Args…>::promise_type,如果协程定义为非右值引用限定的隐式对象成员函数,
  • std:: coroutine_traits < R, cvClassT&&, Args…>::promise_type,如果协程定义为右值引用限定的隐式对象成员函数。
  • std::coroutine_traits<R, Args...>::promise_type, if the coroutine is not defined as an implicit object member function,
  • std::coroutine_traits<R,cvClassT&, Args...>::promise_type, if the coroutine is defined as an implicit object member function that is not rvalue-reference-qualified,
  • std::coroutine_traits<R,cvClassT&&, Args...>::promise_type, if the coroutine is defined as an implicit object member function that is rvalue-reference-qualified.

For example:

If the coroutine is defined as ... then its Promise type is ...
task<void> foo(int x); std::coroutine_traits<task<void>, int>::promise_type
task<void> Bar::foo(int x) const; std::coroutine_traits<task<void>, const Bar&, int>::promise_type
task<void> Bar::foo(int x) &&; std::coroutine_traits<task<void>, Bar&&, int>::promise_type

co_await

一元运算符co_await挂起一个协程并将控制权返回给调用者。它的操作数是一个表达式,要么(1)属于定义成员运算符co_await的类类型,要么可以传递给非成员运算符co_await,要么(2)可以通过当前协程的Promise::await_transform转换为这样的类类型。
The unary operator co_await suspends a coroutine and returns control to the caller. Its operand is an expression that either (1) is of a class type that defines a member operator co_await or may be passed to a non-member operator co_await, or (2) is convertible to such a class type by means of the current coroutine's Promise::await_transform.

co_await expr

co_await表达式只能出现在正则函数体(包括lambda表达式的函数体)中潜在求值的表达式中,不能出现
A co_await expression can only appear in a potentially-evaluated expression within a regular function body (including the function body of a lambda expression), and cannot appear

首先,将expr转换为awaitable对象:
First, expr is converted to an awaitable as follows:

  • 如果expr是由 initial suspend、final suspend或yield表达式生成的,那么awaitable对象就是expr。
  • 否则,如果当前协程的Promise类型具有成员函数await_transform,则awaitable为Promise .await_transform(expr)。
  • 否则就是exptr。
  • if expr is produced by an initial suspend point, a final suspend point, or a yield expression, the awaitable is expr, as-is.
  • otherwise, if the current coroutine's Promise type has the member function await_transform, then the awaitable is promise.await_transform(expr).
  • otherwise, the awaitable is expr, as-is.

然后,像下面这样获得awaiter对象:
Then, the awaiter object is obtained, as follows:

  • 如果运算符co_await的重载解析给出了一个最佳重载,则awaiter是该调用的结果:
    • awaitable.operator co_await()
    • operator co_await(static_cast<Awaitable&&>(awaitable)) for the non-member overload.
  • 否则,如果重载解析没有找到co_await运算符,那么awaiter是可等待的。
  • 否则,如果重载解析不明确,则程序是病态的。
  • if overload resolution for operator co_await gives a single best overload, the awaiter is the result of that call:
    • awaitable.operator co_await() for member overload,
    • operator co_await(static_cast<Awaitable&&>(awaitable)) for the non-member overload.
  • otherwise, if overload resolution finds no operator co_await, the awaiter is awaitable, as-is.
  • otherwise, if overload resolution is ambiguous, the program is ill-formed.

这句话待理解,暂时不翻译
If the expression above is a prvalue, the awaiter object is a temporary materialized from it. Otherwise, if the expression above is a glvalue, the awaiter object is the object to which it refers.

然后,调用await .await_ready()(如果知道结果已经准备好或者可以同步完成,这是避免暂停开销的快捷方式)。如果其结果根据上下文转换为bool,则为false
Then, awaiter.await_ready() is called (this is a short-cut to avoid the cost of suspension if it's known that the result is ready or can be completed synchronously). If its result, contextually-converted to bool is false then

协程被挂起(它的协程状态由局部变量和当前挂起点填充)。
The coroutine is suspended (its coroutine state is populated with local variables and current suspension point).

Awaiter.await_suspend (handle)被调用,其中handle是表示当前协程的协程句柄。在该函数内部,通过该句柄可以观察到暂停的协程状态,该函数负责将其调度到某些执行器上恢复,或将其销毁(返回false计数作为调度)。
awaiter.await_suspend(handle) is called, where handle is the coroutine handle representing the current coroutine. Inside that function, the suspended coroutine state is observable via that handle, and it's this function's responsibility to schedule it to resume on some executor, or to be destroyed (returning false counts as scheduling)

这段就看原文吧,更好理解

  • if await_suspend returns void, control is immediately returned to the caller/resumer of the current coroutine (this coroutine remains suspended), otherwise
  • if await_suspend returns bool,
    • the value true returns control to the caller/resumer of the current coroutine
    • the value false resumes the current coroutine.
  • if await_suspend returns a coroutine handle for some other coroutine, that handle is resumed (by a call to handle.resume()) (note this may chain to eventually cause the current coroutine to resume).
  • if await_suspend throws an exception, the exception is caught, the coroutine is resumed, and the exception is immediately re-thrown.

最后,调用await .await_resume()(无论协程是否被挂起),其结果是整个co_await expr表达式的结果。
Finally, awaiter.await_resume() is called (whether the coroutine was suspended or not), and its result is the result of the whole co_await expr expression.

如果协程在co_await表达式中被挂起,并且稍后被恢复,那么恢复点就在调用await .await_resume()之前。
If the coroutine was suspended in the co_await expression, and is later resumed, the resume point is immediately before the call to awaiter.await_resume().

注意,因为协程在进入awaiter.await_suspend()之前被完全挂起,所以该函数可以自由地跨线程传输协程句柄,而不需要额外的同步。例如,它可以把它放在回调函数中,计划在异步I/O操作完成时在线程池上运行。在这种情况下,由于当前协程可能已经恢复并因此执行了awaiter对象的析构函数,所有的并发执行都是由于await_suspend()在当前线程上继续执行,await_suspend()应该将*this视为已销毁,并且在句柄发布到其他线程后不再访问它。
Note that because the coroutine is fully suspended before entering awaiter.await_suspend(), that function is free to transfer the coroutine handle across threads, with no additional synchronization. For example, it can put it inside a callback, scheduled to run on a threadpool when async I/O operation completes. In that case, since the current coroutine may have been resumed and thus executed the awaiter object's destructor, all concurrently as await_suspend() continues its execution on the current thread, await_suspend() should treat *this as destroyed and not access it after the handle was published to other threads.

Example

Run this code

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>

auto switch_to_new_thread(std::jthread& out)
{
    struct awaitable
    {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h)
        {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // Potential undefined behavior: accessing potentially destroyed *this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
        void await_resume() {}
    };
    return awaitable{&out};
}

struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}

int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

Possible output:

Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224

注意:awaiter对象是协程状态的一部分(作为一个生命周期跨越一个暂停点的临时对象),在co_await表达式完成之前被销毁。它可以用来根据某些异步I/O api的需要维护每个操作的状态,而无需借助额外的动态分配。
Note: the awaiter object is part of coroutine state (as a temporary whose lifetime crosses a suspension point) and is destroyed before the co_await expression finishes. It can be used to maintain per-operation state as required by some async I/O APIs without resorting to additional dynamic allocations.

The standard library defines two trivial awaitables: std::suspend_always and std::suspend_never.

这里省略了不完整的部分,原文中需要自己展开示例,这里忽略

co_yield

co_yield表达式向调用者返回一个值,并挂起当前协程:它是可恢复生成器函数的常见构建块。
co_yield expression returns a value to the caller and suspends the current coroutine: it is the common building block of resumable generator functions.

co_yield` expr

co_yield` braced-init-list

It is equivalent to

co_await promise.yield_value(expr)

一个典型的生成器的yield_value会将它的参数存储(复制/移动或者只是存储地址,因为参数的生命周期会经过co_await中的悬挂点)到生成器对象中,并返回std::suspend_always,从而将控制权转移到调用者/恢复者。
A typical generator's yield_value would store (copy/move or just store the address of, since the argument's lifetime crosses the suspension point inside the co_await) its argument into the generator object and return std::suspend_always, transferring control to the caller/resumer.

Run this code

#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>

template<typename T>
struct Generator
{
    // The class name 'Generator' is our choice and it is not required for coroutine
    // magic. Compiler recognizes coroutine by the presence of 'co_yield' keyword.
    // You can use name 'MyGenerator' (or any other name) instead as long as you include
    // nested struct promise_type with 'MyGenerator get_return_object()' method.

    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;

    struct promise_type // required
    {
        T value_;
        std::exception_ptr exception_;

        Generator get_return_object()
        {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { exception_ = std::current_exception(); } // saving
                                                                              // exception

        template<std::convertible_to<T> From> // C++20 concept
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // caching the result in promise
            return {};
        }
        void return_void() {}
    };

    handle_type h_;

    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // The only way to reliably find out whether or not we finished coroutine,
                // whether or not there is going to be a next value generated (co_yield)
                // in coroutine via C++ getter (operator () below) is to execute/resume
                // coroutine until the next co_yield point (or let it fall off end).
                // Then we store/cache result in promise to allow getter (operator() below
                // to grab it without executing coroutine).
        return !h_.done();
    }
    T operator()()
    {
        fill();
        full_ = false; // we are going to move out previously cached
                       // result to make promise empty again
        return std::move(h_.promise().value_);
    }

private:
    bool full_ = false;

    void fill()
    {
        if (!full_)
        {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // propagate coroutine exception in called context

            full_ = true;
        }
    }
};

Generator<std::uint64_t>
fibonacci_sequence(unsigned n)
{
    if (n == 0)
        co_return;

    if (n > 94)
        throw std::runtime_error("Too big Fibonacci sequence. Elements would overflow.");

    co_yield 0;

    if (n == 1)
        co_return;

    co_yield 1;

    if (n == 2)
        co_return;

    std::uint64_t a = 0;
    std::uint64_t b = 1;

    for (unsigned i = 2; i < n; ++i)
    {
        std::uint64_t s = a + b;
        co_yield s;
        a = b;
        b = s;
    }
}

int main()
{
    try
    {
        auto gen = fibonacci_sequence(10); // max 94 before uint64_t overflows

        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Exception: " << ex.what() << '\n';
    }
    catch (...)
    {
        std::cerr << "Unknown exception.\n";
    }
}

Output:

fib(0)=0
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
fib(9)=34

Notes

Feature-test macro Value Std Feature
__cpp_impl_coroutine 201902L (C++20) Coroutines (compiler support)
__cpp_lib_coroutine 201902L (C++20) Coroutines (library support)
__cpp_lib_generator 202207L (C++23) std::generator: synchronous coroutine generator for ranges

Library support

Coroutine support library defines several types providing compile and run-time support for coroutines.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容

  • 首先,希望读者已经在其他语言或库中了解协程的概念。C++20 终于带来了官方的协程,这是一种无栈的协程实现。 pr...
    Platanuses阅读 695评论 0 0
  • 本文按照 cppreference[https://en.cppreference.com/w/] 列出的特性列表...
    401阅读 21,009评论 2 18
  • 1. C++14 参考文档[https://en.cppreference.com/w/cpp/14] 不同于重量...
    Platanuses阅读 801评论 0 1
  • 回想1999年就开始学习C语言,C++主要用C++98,C++11这两个版本,开发软件从TurboC2.0、Tur...
    老鱼_chaimyu阅读 2,453评论 0 0
  • 0x00 杂记 看了几天,云里雾里。先做点笔记. 写在最前面,协程不会带来性能提升,相反,在drogon项目中实测...
    国服最坑开发阅读 272评论 0 0