На cppreference я прочитал следующее (выделено мной):
Выход за конец сопрограммы эквивалентен co_return;, за исключением того, что поведение не определено, если в области видимости Promise не найдено ни одного объявления return_void.
Но clang 18.1.0 ведет себя по-другому в отношении уничтожения локальных переменных. Когда я падаю с конца сопрограммы, локальные переменные разрушаются, а затем вызывается return_value
. Когда я использую co_return;
, return_value
вызывается перед уничтожением локальных переменных.
Мой вопрос: делает ли cppreference более сильное заявление о co_return; против падения, чем фактический стандарт? Является ли поведение лязгов ошибкой или соответствует стандарту?
Вот небольшой образец, показывающий разницу, а также крестик для вашего удобства:
#include <iostream>
#include <coroutine>
struct Coroutine {
struct promise_type {
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
Coroutine get_return_object() { return {}; }
void unhandled_exception(){}
void return_void() {
std::cout << "return_void called\n";
}
};
};
struct LogOnDestruct {
~LogOnDestruct() {
std::cout << "Destructed\n";
}
};
Coroutine foo() {
LogOnDestruct logger{};
co_await std::suspend_never{};
}
Coroutine bar() {
LogOnDestruct logger{};
co_return;
}
int main() {
foo();
bar();
return 0;
}
Стандарт довольно ясен:
Если
p.return_void()
является допустимым выражением, выход из конца сопрограммы эквивалентенco_return
без операнда; в противном случае выход за пределы сопрограммы приведет к неопределенному поведению.
Структура сопрограммы такова, что определяемое вами тело функции фактически находится внутри области действия более крупной функции, поэтому все локальные переменные этой функции находятся в этой области. Таким образом, co_return
выходит из этой области, как если бы это был оператор goto
. Но это происходит только после вызова соответствующей функции обещания. Таким образом, обещание должно получить вызов функции до того, как будут уничтожены любые локальные переменные.
Я правильно понимаю, что это будет дефектом реализации clangs? (например: правильное поведение — это явное поведение co_return;
, поведение спада — это дефект лязга)
@wutz Ну, co_return expr;
должно произойти до того, как локальные переменные будут уничтожены (я думаю: в противном случае висящие вещи в expr очень опасны), поэтому co_return;
должно имитировать это, а бездействие должно имитировать co_return;
.