理解 promise_type#

协程初始挂起点(The initial-suspend point)#

协程帧创建并初始化完成后,在返回的对象被获取之后,其执行的第一个语句就是 co_await promise.initial_suspend();

co_await promise.initial_suspend(); 表达式的执行结果会被 丢弃,所以此调用返回的 awaiter 对象的 await_resume() 成员函数的返回值类型应当是 void

通过 co_return 从协程返回#

如果协程执行到逻辑体最后也没有一条 co_return 语句,此时就相当于默认在逻辑体最后执行 co_return;。这种情况下,如果定义的 promise_type 本身没有定义 return_void 成员函数,那么此时的执行就可能是未定义的

协程最终挂起点(The final-suspend point)#

对一个在最终挂起点被挂起的协程来说,对其调用 resume() 本身会是一个未定义的行为。对于一个在此时被挂起的协程来说,唯一可以对其进行的操作,只能是 destroy()

编译器选择适当的 promise_type 的原则#

编译器通过 coroutine_traits 来选择合适的 promise_type。对于以下签名的协程:

task<float> foot(std::string x, bool flag);

来说,编译器会将协程返回值类型、参数类型作为模板参数传递给 corotine_traits 来获取合适的 promise_type。所以其最终选择的类型如下:

typename coroutine_traits<task<float>, std::string, bool>::promise_type;

对于非静态的成员协程来说,还需要把类型作为第二个模板参数传递。需要注意的是,对于使用了左值引用的成员协程,此时第二个模板参数也会是一个左值引用。

task<void> my_class::method1(int x) const;
task<foo> my_class::method2() &&;

此时编译器会使用如下版本的 promise_type

// method1 promise type
typename coroutine_traits<task<void>, const my_class&, int>::promise_type;

// method2 promise type
typename coroutine_traits<task<foo>, my_class&&>::promise_type;

默认情况下,coroutine_traits 会查询返回值类型内是否定义了 promise_type 并将其作为最终的 promise_type,其可能实现如下:

namespace std {
    template<typename RET, typename... ARGS>
    struct coroutine_traits<RET, ARGS...> {
        using promise_type = typename RET::promise_type;
    };
}

如果对于这里的返回值类型可以自行定义,那么就可以简单地在其内部指定相应的 promise_type

template<typename T>
struct task {
    using promise_type = task_promise<T>;
    ...
};

但如果不能自行定义,那么就可以尝试让 coroutine_traits 偏特化来实现定义指定:

namespace std {
    template<typename T, typename... ARGS>
    struct coroutine_traits<std::optional<T>, ARGS...> {
        using promise_type = optional_promise<T>;
    };
}