为什么协程的返回类型必须是可移动构造的? [英] Why must the return type of a coroutine be move-constructible?

查看:59
本文介绍了为什么协程的返回类型必须是可移动构造的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Consider the following code that defines the invoker class - a minimal return type for a coroutine. We explicitly delete the copy and move constructors of the invoker class.

#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协程。

The code does not compile on latest GCC (10.1), which is supposed to have full support for C++20 coroutines.

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

Instead, we get an error that indicates that the move constructor is required:

<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;
      |     ^~~~~~~

为什么会这样?

调用者对象是通过调用 get_return_object()构造的invoker_promise ,除非从 f()的调用方访问,否则无法访问。使用C ++ 17保证的复制省略,由 get_return_object()返回的 invoker 是prvalue,因此不应直到从 f()返回之后才实现。

The invoker object is constructed by calling get_return_object() of the invoker_promise, it can't be accessed except from the caller of f(). With C++17 guaranteed copy elision, the invoker returned by get_return_object() is a prvalue, and hence should not be materialized until after it is returned from f().

由于不能从内部访问返回的对象协程,我看不到任何可能需要在返回对象之前实现它的情况。我丢失了什么吗?

Since the returned object cannot be accessed from within the coroutine, I cannot see any situation where we might need to materialize the object before returning it. Am I missing something?

注意:我知道此问题,但它:


  • 是两年前被问到的,

  • 与TS有关协程的版本,

  • 是关于VC ++的实现的,

  • 没有答案,

  • 的注释主要是谈论保证复制省略。

  • was asked two years ago,
  • is about the TS version of coroutines,
  • is about VC++'s implementation,
  • is unanswered, and
  • has comments that mainly talk about guaranteed copy elision.

推荐答案


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

With C++17 guaranteed copy elision, the invoker returned by get_return_object() is a prvalue, and hence should not be materialized until after it is returned from f().

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

That would only be true if a coroutine function call were guaranteed to generate its return value by a call equivalent to building a bunch of objects in a separate stack, then calling get_return_object() on one of them. That is, the question is whether the path from get_return_object() to the function call itself only uses prvalues.

让我们看一下标准怎么说


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

The expression promise.get_­return_­object() is used to initialize the glvalue result or prvalue result object of a call to a coroutine. The call to get_­return_­object is sequenced before the call to initial_­suspend and is invoked at most once.

注意,它说它初始化了 prvalue结果对象。这与<$ c $的行为的定义中使用的语言相同。 c> return 语句

Note that it says that it initializes the "prvalue result object". This is the same language used in the definition of the behavior of the return statement:


return语句初始化的glvalue结果或prvalue结果对象

the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.

我唯一的犹豫就是说标准显然需要保证 get_return_object 之间的省略,协程的调用方是有关 initial_suspend 的最后一部分。因为在 prvalue结果对象的初始化和将控制权返回给调用方之间发生了 ,所以可能必须存在一个中介,必须从中复制/移动该中介。

The only hesitation I would have in saying that the standard clearly requires guaranteed elision between get_return_object and the caller of the coroutine is the last part about initial_suspend. Because something happens between the initialization of the "prvalue result object" and returning control to the caller, it could be that there has to be an intermediary, which must be copied/moved from.

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

But the fact that it's using the exact same language as return suggests that it ought to be providing the exact same behavior too.

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

When running on MSVC's coroutine implementation, your code (with only minor changes for differences in where certain types are defined) works fine. Coupled with the above evidence, I would say that this suggests that this is a compiler bug.

这篇关于为什么协程的返回类型必须是可移动构造的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆