为什么协程的返回类型必须是可移动构造的? [英] Why must the return type of a coroutine be move-constructible?
问题描述
考虑以下代码,该代码定义 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 byget_return_object()
is a prvalue, and hence should not be materialized until after it is returned fromf()
.
只有协程函数调用可以通过等于在单独的堆栈中构建一堆对象的调用来保证生成其返回值,然后在其中一个对象上调用 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 toget_return_object
is sequenced before the call toinitial_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屋!