c++ - Does coroutine_handle support polymorphic promises? - Stack Overflow

admin2025-04-16  6

I am specifically referring to the from_promise method.

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?

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?

Share Improve this question edited Feb 3 at 10:55 Alexandre S. asked Feb 2 at 9:24 Alexandre S.Alexandre S. 1978 bronze badges 6
  • Please edit your question with a concrete code example. – Brian Bi Commented Feb 2 at 22:07
  • @BrianBi Done. I was (and still am) not sure it would add much to the question, but I guess I might just be lacking perspective here – Alexandre S. Commented Feb 3 at 10:58
  • For such use-case - just use std::coroutine_handle<> handle;. It is not clear why you need GenericPromise in coroutine_handle. – PiotrNycz Commented Feb 4 at 21:26
  • 1 Anyway here en.cppreference.com/w/cpp/coroutine/coroutine_handle/… the description is clear - it is UB to mess with different types than real one – PiotrNycz Commented Feb 4 at 21:30
  • @PiotrNycz my goal is to expose some data/behavior common to all my promise types through GenericPromise, so I can use a return object that is independent of the actual SpecialisedPromise but can still have meaningful interactions with it. – Alexandre S. Commented Feb 5 at 7:25
 |  Show 1 more comment

1 Answer 1

Reset to default 3

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

转载请注明原文地址:http://anycun.com/QandA/1744805584a87867.html