В сопрограммах C++ функция-член final_suspend
типа обещания может возвращать любой произвольный ожидаемый тип, хотя обычно это std::suspend_always
, которая оставляет ответственность за уничтожение сопрограммы на кого-то другого, или std::suspend_never
, которая автоматически уничтожает сопрограмму. Последнее, по-видимому, является особым случаем, поскольку обычно не разрешается возобновлять сопрограмму, находящуюся в конечной точке приостановки. Однако в ходе моего тестирования кажется, что это особое поведение применяется неравномерно.
Суть проблемы заключается в том, что функция-член await_suspend
awaitable может иметь любой из трех различных типов возвращаемого значения: если она возвращает void
, сопрограмма остается приостановленной. Если возвращается bool
, выполнение сопрограммы возобновляется для false
и остается приостановленным для true
. Если он возвращает std::coroutine_handle
, эта сопрограмма возобновляется.
Я протестировал поведение всех этих результатов с возвращаемым значением final_suspend
, и все три MSVC, Clang и GCC сходятся в этом поведении для ожидаемого объекта, где await_ready
всегда возвращает false
:
final_suspend().await_suspend(handle)
возвращает void
, сопрограмма остается приостановленной в конечной точке приостановки, как и ожидалось.final_suspend().await_suspend(handle)
возвращает bool(true)
, сопрограмма и в этом случае остается приостановленной в своей конечной точке приостановки.final_suspend().await_suspend(handle)
возвращает bool(false)
, то, что интересно, сопрограмма автоматически уничтожается, что видно по выполняемому деструктору типа обещания. Это согласуется с поведением std::suspend_never
и показывает, что решение может быть принято во время выполнения. Также интересно отметить, что await_resume
вызывается и перед уничтожением сопрограммы, хотя ее результат отбрасывается.final_suspend().await_suspend(handle)
возвращает дескриптор другой сопрограммы, эта сопрограмма возобновляется, как и ожидалось, а текущая сопрограмма остается приостановленной в своей конечной точке приостановки.final_suspend().await_suspend(handle)
возвращает тот же дескриптор, что и задан, программа аварийно завершает работу!Результаты 1, 2 и 4 соответствуют ожиданиям. Результаты 3 и 5 интересуют меня больше всего - с определенной точки зрения можно подумать, что к ним будут относиться одинаково, поскольку в обоих случаях они пытаются «возобновить» завершенную сопрограмму, но только результат 3, кажется, получает специальную обработку. для автоматического уничтожения сопрограммы. Из-за этого немного неудобно решать это во время выполнения при возврате дескриптора сопрограммы, поскольку вместо этого вам придется вручную уничтожить сопрограмму, а затем вернуть неактивный дескриптор сопрограммы, что немного сложнее, чем просто возврат false
для того же эффекта. и может даже сделать некоторые варианты использования невозможными из-за необходимости возвращать дескриптор со стертым типом.
Мой вопрос: где именно эти конкретные результаты описаны или предусмотрены в стандарте C++? Когда я смотрю, я не могу найти многого. Стандарт выглядит довольно кратким в описании поведения сопрограммы. Кажется, что особое поведение final_suspend
awaitable важно задокументировать, но я не могу его найти. Я могу найти утверждение, что final_suspend
и использование его awaitable должно быть noexcept
(«не должно быть потенциально-выбрасывающим»), но это все, что касается специальных правил, насколько я могу найти. Какой раздел мне не хватает? Почему возврат того же самого переданного дескриптора приводит к иному поведению, чем возврат false
?
Когда await_suspend
возвращается false
, сопрограмма возобновляется . Это приводит к тому, что управление уходит с конца сопрограммы, что разрушает состояние сопрограммы.
Когда await_suspend
возвращает дескриптор, для этого дескриптора вызывается возобновить(). Это нарушает предварительное условие из resume()
, поэтому поведение не определено.
Ух ты, это очень тонкая разница между утверждением о возобновлении сопрограммы и утверждением о том, что возобновление вызывается, что возобновляет сопрограмму. Учитывая, что эти предложения настолько близки друг к другу, можно подумать, что они предназначены для одинаковой интерпретации. Уверены ли мы, что это решающая разница? Возможно, я просто не привык к тому, насколько лаконичным может быть стандарт.