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

ppcbkaq5  于 2022-12-27  发布在  其他
关注(0)|答案(2)|浏览(104)

在我的C++协程库中的一些地方,我需要遍历一个相互等待的挂起的协程链。
例如,假设Foo0调用Foo1Foo1调用Foo2Foo2调用FooN,每个co_await都是下一个的结果,FooN当前被挂起,它们的promise类型存储等待者的句柄,以便在它们完成时恢复。因此它们形成了一个从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.恢复或销毁它的协同程序。
1.获取对它的已知基类的引用。(如果有帮助的话,我可以保证没有多重继承。)
这将允许我解决我的问题,因为我可以将awaiter成员放入一个所有承诺都继承自的公共无模板基类中,并遍历这些承诺的列表。
我很肯定在实践中,这样做可能会奏效:

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

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

  • 在C++20草稿中,coroutine.handle.export.import(https://timsong-cpp.github.io/cppwp/n4868/coroutine.handle.export.import)列出了std::coroutine_handle<Promise>::from_address的先决条件"addr是通过先前对address的调用获得的",而没有说明任何关于address的被调用者的类型。
  • latest draft中,它特别指出"addr是通过在类型为 * cv * coroutine_­handle<Promise>的对象上对address的先前调用获得的",如果Promise实际上是real promise类型的基类,那么这就不是真的。
icnyk63a

icnyk63a1#

我认为下面的方法在实践中是可行的,至少在没有多重继承或虚函数的简单情况下(即基类和promise类都是pointer-interconvertible):

// 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可以在协程链上变化。
我读了C20草案和最新的标准草案,都说get_base_handle的实现是法律的的:std::coroutine_handle<PromiseBase>::from_promise的前提条件仅仅是输入引用是“对协程的承诺对象的引用”。在该上下文中,*this肯定是这样,并且即使当被转换为对PromiseBase的引用时,大概仍然是这样。
我说这“可能在实践中起作用”,是因为我对标准的意图不是100%有信心。也许这意味着要禁止?但如果是这样,肯定不清楚。在缺乏明确措辞的情况下,也值得考虑可实施性。而且我看不出有什么理由不能正确地工作,因为现在clang/libc
中已经实现了协程句柄(void*指针,指向在已知偏移处具有承诺的协同程序帧);至少在没有多重继承和虚函数的情况下,这似乎是正确的。
如果标准的意图是说这是不法律的的,那么我认为措辞可以改进。我已经在标准的GitHub repo上提交了一个issue,要求澄清。

nue99wik

nue99wik2#

我们无法从std::coroutine_handle<>那里得到任何形式的承诺,这基本上就是这种专门化的全部意义:与协程交互而不关心它包含什么类型的承诺。
但这可能是未定义的行为:
LWG issue 3460。它明确地表明coroutine_handle<T>::from_address要求地址是从coroutine_handle<T>的示例中获得的。这是C++20的一个缺陷,所以它是标准的一部分。

相关问题