Date: Tue, 3 Jan 2023 10:18:40 -0500
On Mon, Jan 2, 2023 at 7:24 PM Aaron Jacobs via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Hello all,
>
> While working on a C++ coroutine library for use at Google I've found that
> some
> important functionality is hindered by a precondition on
> `std::coroutine_handle<Promise>::from_promise` that seems to be very
> slightly
> stronger than necessary, and I'm seeking feedback on a proposal to weaken
> it
> accordingly. I think this can be done while keeping existing
> implementations
> conforming.
>
> [coroutine.handle.con] currently lists the following precondition for
> `from_promise(Promise& p)`:
>
> > Preconditions: `p` is a reference to a promise object of a coroutine.
>
> I propose changing it to something like the following (feedback on wording
> welcome):
>
> > Preconditions: p is a reference to an object that is
> > pointer-interconvertible with the promise object of a coroutine, and
> > has the same alignment as the promise.
>
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`. There's certainly nothing wrong or
undefined about taking a pointer-that-physically-points-to-an-X-object and
casting it to type pointer-to-X.
struct X { int i; };
void from_promise(X&);
void test() {
X x;
int *pi = &x.i;
from_promise(*pi); // not allowed, obviously
from_promise(*(X*)pi); // already allowed, and perfectly well-defined
and safe
}
So if this is *all* you mean, then you don't need to change the Standard at
all; you just need to add a cast to your code.
But maybe you mean something more?
Promise types naturally form a
> singly-linked list via coroutine handles to resume when a coroutine
> completes:
> [...]
> template <typename Result>
> struct MoreRealisticPromise {
> [...]
> // The value co_returned by the coroutine.
> std::optional<Result> result;
> // The coroutine to resume when my coroutine finishes.
> std::coroutine_handle<???> waiter;
> };
>
> In the example above we could use `std::coroutine_handle<>` for the
> waiter, but
> then we could no longer use it as a link in a list because there would be
> no
> UB-free way to get hold of the next node given a current node.
You mean, there would be no way to take that std::coroutine_handle<void>
and convert it back to a std::coroutine_handle<MoreRealisticPromise<U>>?
Well, sure, but that's mainly because you have no idea what type `U` should
actually be. It's not guaranteed that `U` is the same type as `Result`. If
you *knew* what type `U` was, then you wouldn't have a problem; and since
you *don't* know what type `U` is, you *do* have a problem. Messing around
with the semantic requirements of `from_promise` doesn't seem to do
anything to solve that problem.
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."
[...] there
> seems to be no UB-free wait to obtain a
> `std::coroutine_handle<PromiseBase>`. A
> reference to the `PromiseBase` sub-object of `Promise<int>` is not
> technically
> a reference to a coroutine's promise object.
>
It's not clear to me what you mean here. Of course a reference to a given
physical object X *is* a reference to a coroutine's promise object, *if and
only if* the object X is a coroutine's promise object. The *type* of the
reference (whether it's a reference-to-most-derived-object or
reference-to-base or whatever) doesn't seem relevant.
I think it'll greatly clarify things if you show a complete compilable
example (*as simple as possible*) that exercises the `from_promise`
codepath you want to change.
–Arthur
std-proposals_at_[hidden]> wrote:
> Hello all,
>
> While working on a C++ coroutine library for use at Google I've found that
> some
> important functionality is hindered by a precondition on
> `std::coroutine_handle<Promise>::from_promise` that seems to be very
> slightly
> stronger than necessary, and I'm seeking feedback on a proposal to weaken
> it
> accordingly. I think this can be done while keeping existing
> implementations
> conforming.
>
> [coroutine.handle.con] currently lists the following precondition for
> `from_promise(Promise& p)`:
>
> > Preconditions: `p` is a reference to a promise object of a coroutine.
>
> I propose changing it to something like the following (feedback on wording
> welcome):
>
> > Preconditions: p is a reference to an object that is
> > pointer-interconvertible with the promise object of a coroutine, and
> > has the same alignment as the promise.
>
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`. There's certainly nothing wrong or
undefined about taking a pointer-that-physically-points-to-an-X-object and
casting it to type pointer-to-X.
struct X { int i; };
void from_promise(X&);
void test() {
X x;
int *pi = &x.i;
from_promise(*pi); // not allowed, obviously
from_promise(*(X*)pi); // already allowed, and perfectly well-defined
and safe
}
So if this is *all* you mean, then you don't need to change the Standard at
all; you just need to add a cast to your code.
But maybe you mean something more?
Promise types naturally form a
> singly-linked list via coroutine handles to resume when a coroutine
> completes:
> [...]
> template <typename Result>
> struct MoreRealisticPromise {
> [...]
> // The value co_returned by the coroutine.
> std::optional<Result> result;
> // The coroutine to resume when my coroutine finishes.
> std::coroutine_handle<???> waiter;
> };
>
> In the example above we could use `std::coroutine_handle<>` for the
> waiter, but
> then we could no longer use it as a link in a list because there would be
> no
> UB-free way to get hold of the next node given a current node.
You mean, there would be no way to take that std::coroutine_handle<void>
and convert it back to a std::coroutine_handle<MoreRealisticPromise<U>>?
Well, sure, but that's mainly because you have no idea what type `U` should
actually be. It's not guaranteed that `U` is the same type as `Result`. If
you *knew* what type `U` was, then you wouldn't have a problem; and since
you *don't* know what type `U` is, you *do* have a problem. Messing around
with the semantic requirements of `from_promise` doesn't seem to do
anything to solve that problem.
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."
[...] there
> seems to be no UB-free wait to obtain a
> `std::coroutine_handle<PromiseBase>`. A
> reference to the `PromiseBase` sub-object of `Promise<int>` is not
> technically
> a reference to a coroutine's promise object.
>
It's not clear to me what you mean here. Of course a reference to a given
physical object X *is* a reference to a coroutine's promise object, *if and
only if* the object X is a coroutine's promise object. The *type* of the
reference (whether it's a reference-to-most-derived-object or
reference-to-base or whatever) doesn't seem relevant.
I think it'll greatly clarify things if you show a complete compilable
example (*as simple as possible*) that exercises the `from_promise`
codepath you want to change.
–Arthur
Received on 2023-01-03 15:18:53