On cppreference I read the following (emphasis mine):
Falling off the end of the coroutine is equivalent to co_return;, except that the behavior is undefined if no declarations of return_void can be found in the scope of Promise.
But clang 18.1.0 behaves differently in regards to destruction of local variables. When I fall off the end of a coroutine, the local variables get destructed, then return_value
is called. When I use co_return;
, return_value
is called before destruction of local variables.
My question is: is cppreference making a stronger statement about co_return; vs falling off than the actual standard is? Is clangs behavior a bug, or aligned with the standard?
Here a small sample that shows the difference, along with a godbolt for your convenience:
#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;
}
The standard is pretty clear:
If a search for the name
return_void
in the scope of the promise type finds any declarations, flowing off the end of a coroutine is equivalent to aco_return
with no operand; otherwise flowing off the end of a coroutine results in undefined behavior.
The structure of a corotuine is such that the function body you define is effectively inside the scope of a larger function, so all of that function's local variables are in that scope. As such, a co_return
exits this scope as if by a goto
statement. But it only does that after calling the appropriate promise function. So the promise ought to get the function call before any local variables are destroyed.