为什么协程的返回类型必须是可移动构造的?

Why must the return type of a coroutine be move-constructible?

本文关键字:可移动 返回类型 为什么      更新时间:2023-10-16

请考虑以下定义invoker类的代码 - 协程的最小返回类型。 我们显式删除invoker类的复制和移动构造函数。

#include <coroutine>
#include <cstdlib>
class invoker {
public:
class invoker_promise {
public:
invoker get_return_object() { return invoker{}; }
auto initial_suspend() { return std::suspend_never{}; }
auto final_suspend() { return std::suspend_never{}; }
void return_void() {}
void unhandled_exception() { std::abort(); }
};
using promise_type = invoker_promise;
invoker() {}
invoker(const invoker&) = delete;
invoker& operator=(const invoker&) = delete;
invoker(invoker&&) = delete;
invoker& operator=(invoker&&) = delete;
};
invoker f() {
co_return;
}

该代码不会在最新的 GCC (10.1( 上编译,该 GCC 应该完全支持 C++20 个协程。

相反,我们收到一个错误,指示需要移动构造函数:

<source>: In function 'invoker f()':
<source>:23:1: error: use of deleted function 'invoker::invoker(invoker&&)'
23 | }
| ^
<source>:17:5: note: declared here
17 |     invoker(invoker&&) = delete;
|     ^~~~~~~

为什么会这样?

invoker对象是通过调用invoker_promiseget_return_object()构造的,除了从f()的调用方之外无法访问。 使用 C++17 保证复制省略,get_return_object()返回的invoker是一个 prvalue,因此在从f()返回之前不应实现。

由于无法从协程中访问返回的对象,因此我看不到任何可能需要在返回对象之前具体化对象的情况。 我错过了什么吗?

注意:我知道这个问题,但它:

  • 两年前有人问,
  • 是关于协程的 TS 版本,
  • 是关于VC++的实现,
  • 未回答,并且
  • 有评论主要谈论保证复制省略。

使用 C++17 保证复制省略,get_return_object()返回的invoker是一个 prvalue,因此在从f()返回之前不应实现。

仅当保证协程函数调用通过相当于在单独的堆栈中构建一堆对象,然后在其中一个对象上调用get_return_object()的调用来生成其返回值时,才是正确的。也就是说,问题是从get_return_object()到函数调用本身的路径是否仅使用 prvalues。

让我们看看标准是怎么说的:

表达式promise.get_­return_­object()用于初始化对协程调用的 glvalue result 或 prvalue result 对象。对get_­return_­object的调用在调用initial_­suspend之前排序,最多调用一次。

请注意,它说它初始化了"prvalue result 对象"。这与定义return语句的行为时使用的语言相同:

return 语句通过从操作数复制初始化来初始化(显式或隐式(函数调用的 GLvValue 结果或 PRvValue 结果对象。

在说标准明确要求get_return_object和协程的调用者之间保证省略时,我唯一会犹豫的是关于initial_suspend的最后一部分。由于在初始化"prvalue result 对象"和将控制权返回给调用方之间发生了一些事情,因此可能必须有一个中介,必须从中复制/移动该中介。

但它使用与return完全相同的语言这一事实表明它也应该提供完全相同的行为

在 MSVC 的协程实现上运行时,您的代码(仅对定义某些类型的差异进行微小更改(工作正常。结合上述证据,我会说这表明这是一个编译器错误。

此错误已在 GCC 10.2 中修复,但目前存在于 Clang 主干中,已报告错误。演示:https://gcc.godbolt.org/z/9sYj1qMnc