C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Allowing coroutine_handle::from_promise to accept a pointer-interconvertible object

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Wed, 4 Jan 2023 01:50:40 +0000
On Tue, 3 Jan 2023 at 23:33, Aaron Jacobs via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Wed, Jan 4, 2023 at 2:18 AM Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
> > Just looking at this proposed wording, it doesn't seem like your
> > proposal does anything. A pointer that points to "an object
> > pointer-interconvertible with X, with the same alignment as X" is
> > exactly the same, machine-wise, as a pointer that points to "an
> > X" — all you have to do is cast it to (X*) before passing it to
> > `from_promise`.
>
> I agree with Jason: while you are totally right in a practical sense (and
> that
> is why there is no compatibility issue with existing implementations), the
> standard says this is not allowed, because a reference to a base class
> sub-object of a coroutine promise is not a reference to a coroutine
> promise,
> even if they happen to have the same address This is also what @timsong-cpp
> said in https://github.com/cplusplus/draft/issues/6039.
>
> This is exactly why I want to change the precondition: it forbids something
> that is completely practical, where the machine can't tell the difference
> in
> real implementations. The precondition seems to just be tighter than
> necessary.
>
> (Why do I care at all if the machine can't tell the difference? Well,
> relying
> on UB is bad, and you can imagine in the future some sanitizer or other
> runtime
> check causes this assumption to break in some way. Perhaps it stores
> information about the type in the coroutine handle, not just the address.)
>
>
> > Your code snippets don't actually involve any calls to
> > `from_promise`. Maybe it would be clearer if you showed a Tony Table,
> > like "here's something that doesn't compile / doesn't work today"
> > versus "here's how it would work after this proposal."
>
> Sorry, I thought it would be clear from context. I'm just looking for a
> UB-free
> way to create a `std::coroutine_handle<PromiseBase>` from a `Promise&`;
> something like the following:
>
> // Called when my coroutine co_awaits the result of another
> // coroutine whose promise also inherits from
> // CoroutinePromiseBase in a pointer-interconvertible way.
> template <typename MyResult>
> template <typename TheirResult>
> void Promise<MyResult>::WaitFor(Promise<TheirResult>& child) {
> [...]
>
> // Tell the child coroutine to resume the parent coroutine
> // after it finishes.
> child.waiter =
> std::coroutine_handle<PromiseBase>::from_promise(*this);
>
> [...]
> }
>
> As both you and I said above, this actually works fine in a practical sense
> today: it compiles, and causes no runtime issues with the real
> implementation
> in clang (and I expect gcc and MSVC too). But I think it is technically
> undefined behavior.
>

 If I've understood correctly, this is a minimal example of what you want
to work:

struct promise_base {
    // ...
    template<class T>
    void set_previous(std::coroutine_handle<promise<T>> h) noexcept {
        previous =
std::coroutine_handle<promise_base>::from_promise(h.promise()); // !!!
    }
    void log_chain() {
        std::cout << this << '\n';
        if (previous)
            previous.promise().log_chain(); // motivation
    }
    std::coroutine_handle<promise_base> previous; // not just
std::coroutine_handle<>
};

template<class T>
struct promise : promise_base {
    // ...
};

https://godbolt.org/z/cz646Tn7s for the full working example, largely
cribbed from cppreference.

As you said, it does happen to work as long as `promise_base` is
pointer-interconvertible with `promise<T>` and the latter is not more
aligned.

Presumably it would be possible to do this in a compliant manner by storing
both a `promise_base*` and a fully-erased `std::coroutine_handle<>`. But
that would be wasteful even if indeed it does work.

Perhaps an alternative would be for the library to perform the conversion
e.g. with a suitably constrained converting constructor
`std::coroutine_handle<P>::coroutine_handle(coroutine_handle<Q>)`.

Received on 2023-01-04 01:50:54