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

Ber*_*ard 8 c++ coroutine c++20

考虑以下定义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;
}
Run Code Online (Sandbox Code Playgroud)

代码不能在最新的 GCC (10.1)上编译,它应该完全支持 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;
      |     ^~~~~~~
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

invoker目的是通过调用构造get_return_object()invoker_promise,它不能被除了从呼叫者访问f()。使用 C++17 保证复制省略,invoker返回的 byget_return_object()是一个纯右值,因此在从 返回之前不应具体化f()

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

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

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

Nic*_*las 5

\n

使用 C++17 保证的复制省略,invoker返回的 byget_return_object()是纯右值,因此在从 中返回之前不应具体化f()

\n
\n\n

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

\n\n

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

\n\n
\n

该表达式promise.get_\xc2\xadreturn_\xc2\xadobject()用于初始化协程调用的泛左值结果或纯右值结果对象。对 的调用按get_\xc2\xadreturn_\xc2\xadobject顺序排列在对 的调用之前initial_\xc2\xadsuspend,并且最多被调用一次。

\n
\n\n

请注意,它说它初始化“纯右值结果对象”。这与语句行为return定义中使用的语言相同:

\n\n
\n

return 语句通过从操作数进行复制初始化来初始化(显式或隐式)函数调用的泛左值结果或纯右值结果对象。

\n
\n\n

我唯一犹豫的是,该标准明确要求保证协程的get_return_object调用者之间的省略是关于 的最后一部分initial_suspend。因为“纯右值结果对象”的初始化和将控制返回给调用者之间发生了一些事情,所以可能必须有一个必须从中复制/移动的中介。

\n\n

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

\n\n

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

\n