Search code examples
c++c++20c++-coroutine

Coroutine return_void; difference between co_return; and falling off the end


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;
}

Solution

  • 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 a co_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.