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

Does coroutine_handle support polymorphic promises?


I am specifically referring to the from_promise method.

https://en.cppreference.com/w/cpp/coroutine/coroutine_handle/from_promise

Would this function behave correctly (and in a portable way) if I tried to get a coroutine_handle from a reference to a SpecialisedPromise? Similar question with from_address

Code example:

struct Foo
{
    /* ... */
};

struct GenericPromise
{
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
    virtual ~GenericPromise() = default;
};

struct Coroutine
{
    std::coroutine_handle<GenericPromise> handle;
    struct promise_type : Foo, GenericPromise
    {
        Coroutine get_return_object()
        {
            return { std::coroutine_handle<GenericPromise>::from_promise(*this) };
        }

        int AdditionalData = 42;
    };
};

Coroutine Coro()
{
    co_return;
}

int main(int argc, char* argv[])
{
    Coroutine coro = Coro();

    coro.handle.resume();
    assert(coro.handle.done());
    coro.handle.destroy();
}

Is this program guaranteed to work as expected, or is the access through a different-typed coroutine_handle a potential bug/UB?


Solution

  • You can keep handle to the given coroutine - either with std::coroutine_handle<> (which is std::coroutine_handle<void>) or via std:::coroutine_handle<PromiseType> where PromiseType is exact type of your coroutines.

    See what coroutine_handle::from_address cppreference says:

    The behavior is undefined if addr is neither a null pointer value nor an underlying address of a coroutine_handle. The behavior is also undefined if the addr is an underlying address of a std::coroutine_handle, where both Promise and P1 are not void, and P1 is different from Promise.

    Imagine that coroutine_handle is just some wrapper on void* to promise object. In no coroutine world it is also UB when casting from P* to void*, then from void* to P1* if !std::is_same_v<P, P1>

    And similarly in coroutine_handle::from_promise

    The behavior is undefined if p is not a reference to a promise object.

    So - it can't be reference to base subobject of coroutine - it has to be reference to the coroutine object - otherwise it is UB to use from_promise.

    Anyway - in your case - I would consider to store std::coroutine_handle<> and pointer to your "generic" base class:

    struct Coroutine
    {
        std::coroutine_handle<> handle;
        GenericPromise* genericPromise;
        struct promise_type : Foo, GenericPromise
        {
            Coroutine get_return_object()
            {
                return { .handle = std::coroutine_handle<promise_type>::from_promise(*this),
                          .genericPromise = this };
            }
    
            int AdditionalData = 42;
        };
    };