提问者:小点点

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


请考虑以下定义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)上编译,后者应该完全支持C++20协同。

相反,我们会得到一个错误,该错误指示move构造函数是必需的:

<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()返回它之前不应该物化。

由于返回的对象不能从协同关系中访问,所以我看不到在返回对象之前需要物化该对象的任何情况。 我是不是漏掉了什么?

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

  • 两年前被问到,
  • 是关于coroutines的TS版本,
  • 是关于VC++的实现,
  • 未应答,并且
  • 的注释主要涉及保证复制删除。

共1个答案

匿名用户

在C++17保证复制删除的情况下,get_return_object()返回的invoker是一个prvalue,因此在从f()返回它之前不应该物化。

只有在保证协同函数调用生成返回值的情况下,才会这样做,该调用相当于在单独的堆栈中构建一堆对象,然后对其中一个对象调用get_return_object()。 也就是说,问题是从get_return_object()到函数调用本身的路径是否只使用PRValues。

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

表达式promise.get_return_object()用于初始化对协同项的调用的glvalue结果或prvalue结果对象。 对get_return_object的调用在对initial_suspend的调用之前被排序,并且最多被调用一次。

注意,它说它初始化了“PRValue结果对象”。 这与定义return语句的行为时使用的语言相同:

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

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

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

当在MSVC的协同实现上运行时,您的代码(仅对某些类型定义位置的差异进行了较小的更改)可以正常工作。 再加上上面的证据,我会说这提示这是一个编译器bug。