请考虑以下定义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_promise
的get_return_object()
来构造的,除非从f()
的调用方访问它。 在C++17保证复制删除的情况下,get_return_object()
返回的invoker
是一个prvalue,因此在从f()
返回它之前不应该物化。
由于返回的对象不能从协同关系中访问,所以我看不到在返回对象之前需要物化该对象的任何情况。 我是不是漏掉了什么?
注意:我知道这个问题,但它:
在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。