是否有一种符合标准的C++方法来获取对协程承诺的已知基类的引用?



在我的C++协程库中的几个地方,我需要走一连串相互等待的挂起协程。

例如,假设Foo0调用Foo1,这调用Foo2,哪个...调用FooN,每个co_await是下一个的结果,并且FooN当前处于挂起状态。它们的承诺类型存储等待者的句柄,以便在完成后恢复,因此它们形成了从FooNFoo0的单链列表。我希望能够迭代列表,例如在取消后进行清理或为分析提供很好的异步堆栈跟踪。

如果所有协程都具有相同的 promise 类型,只要我有最后一个协程帧的句柄,这很容易。我可以实现它,如下所示:

void WalkCoroutineChain(std::coroutine_handle<Promise> h) {
while (h) {
// ... do something with h ...
Visit(h);

// Move on to the caller of h's coroutine.
h = h.promise().awaiter;
}
}

问题在于,实际上它们并不都具有相同的承诺类型。它们共享一个公共承诺类型模板,甚至共享一个用于承诺类型的公共基类,但承诺类型本身不同,因为它是根据它们返回的结果类型模板化的。


我的问题是:给定对承诺的引用,是否有任何合法的方法将其存储为类型擦除的东西,如void*std::coroutine_handle<>,同时保留通过该类型擦除值执行以下两项操作的能力?

  1. 恢复或销毁其协程。
  2. 获取对其已知基类的引用。 (如果有帮助,我可以保证没有多重继承。

这将允许我解决我的问题,因为我可以将awaiter成员放入所有承诺都继承自的公共非模板化基类中,并浏览这些基类的列表。

我很确定在实践中这样做可能会奏效:

std::coroutine_handle<> type_erased = GetHandleSomehow();
auto base = std::coroutine_handle<PromiseBase>::from_address(type_erased.address());

但这可能是未定义的行为:

  • 在C++20草案中[coroutine.handle.export.import]列出了std::coroutine_handle<Promise>::from_address"addr是通过事先调用address获得的"的先决条件,而没有说明address被调用者的类型。

  • 在最新的草案中,它特别说"addr是通过事先调用cvcoroutine_­handle<Promise>类型的对象address获得的",如果Promise实际上是真实承诺类型的基类,那就不是真的了。

我认为以下内容在实践中可能有效,至少在没有多个继承或虚函数的简单情况下(即当基类和 promise 类是指针可相互转换时):

// Assume the promise inherits from a base class.
struct PromiseBase {
// The handle to resume when done, also used for walking the linked list as
// described in the question.
std::coroutine_handle<PromiseBase> awaiter;
};
template <typename Result>
struct Promise : PromiseBase {
std::coroutine_handle<PromiseBase> get_base_handle() {
std::coroutine_handle<PromiseBase>::from_promise(*this);
}
};

PromiseBase::awaiter可以用作WalkCoroutineChain函数中的链接,而不是std::coroutine_handle<Promise<T>>,因为T在协程链中可能有所不同。


我读了 C++20 草案和最新的标准草案,都说get_base_handle的实施是合法的:std::coroutine_handle<PromiseBase>::from_promise的前提只是输入引用是"对协程的承诺对象的引用"。在这种情况下,*this肯定是正确的,即使转换为对PromiseBase的引用,大概仍然是正确的。

我说这"在实践中可能有效",因为我对标准的意图没有100%的信心。也许这是要被禁止的?但如果是这样,肯定不清楚。在没有明确措辞的情况下,也值得考虑可实现性,鉴于今天在 clang/libc++ 中如何实现协程句柄(指向协程帧的void*指针,承诺在已知偏移量处),我认为没有理由无法正常工作);至少在没有多重继承和虚函数的情况下,这似乎是正确的。

如果标准的意图是说这是不合法的,那么我认为措辞可以改进。我已经在标准的GitHub存储库上提出了一个问题,要求澄清。

没有办法从std::coroutine_handle<>那里得到任何形式的承诺。这基本上就是这种专业化的全部意义所在:与协程交互而不关心它包含什么类型的承诺。

但这可能是未定义的行为:

LWG问题3460。它特别明确指出,coroutine_handle<T>::from_address要求地址是专门从coroutine_handle<T>实例中获取的。这是作为 C++20 的缺陷应用的,因此它是标准的一部分。

最新更新