为什么协程的返回类型必须是可移动构造的?
Why must the return type of a coroutine be move-constructible?
请考虑以下定义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_promise
的get_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
- 如何获取std::result_of函数的返回类型
- 为什么协程的返回类型必须是可移动构造的?
- 如何获取类型是否真正可移动可构造
- 对于可移动类型,按值传递比重载函数更好吗?
- 具有抑制移动构造/赋值的类型如何仍被视为可移动类型?
- 在可移动类型的构造函数 lambda 中捕获此内容的安全使用
- 从类返回可移动成员变量
- 在成员容器中复制具有仅可移动但可克隆类型的构造函数
- C++11/VS2010:返回包含不可复制但可移动对象的容器
- C++移动分配运算符返回类型
- 使用方法捕获模板化可调用对象的返回类型
- 构造函数中可移动类型的 C++ 右值
- 如何按值返回一个不可复制的、可移动的对象作为const并存储它
- 在VS2010中获取可调用类型的返回类型
- Void_t和带有decltype的尾随返回类型是完全可互换的吗?
- 可移动类型的类型特征
- 对于可移动类型,删除std::向量中间的元素仍然很昂贵吗
- 为什么 C++17 的 std::any 不允许按 any_cast 返回可移动值?
- 返回类型和移动语义
- 这个可移动类型对于 std::map 有什么问题?