Issue 3460: Unimplementable noop_coroutine_handle guarantees (original) (raw)
This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++23 status.
3460. Unimplementable noop_coroutine_handle
guarantees
Section: 17.13.5.2.4 [coroutine.handle.noop.resumption] Status: C++23 Submitter: Casey Carter Opened: 2020-07-01 Last modified: 2023-11-22
Priority: 2
View all issues with C++23 status.
Discussion:
17.13.5.2.4 [coroutine.handle.noop.resumption]/2 states "Remarks: If noop_coroutine_handle
is converted to coroutine_handle<>
, calls to operator()
, resume
and destroy
on that handle will also have no observable effects." This suggests that e.g. in this function:
void f(coroutine_handle<> meow) { auto woof = noop_coroutine(); static_cast<coroutine_handle<>&>(woof) = meow; static_cast<coroutine_handle<>&>(woof).resume(); }
the final call to coroutine_handle<>::resume
must have no effect regardless of what coroutine (if any) meow
refers to, contradicting the specification of coroutine_handle<>::resume
. Even absent this contradiction, implementing the specification requires coroutine_handle<>::resume
to determine if *this
is a base subobject of a noop_coroutine_handle
, which seems pointlessly expensive to implement.
17.13.5.2.6 [coroutine.handle.noop.address]/2 states "Remarks: A noop_coroutine_handle
's ptr
is always a non-null pointer value." Similar to the above case, a slicing assignment of a default-initialized coroutine_handle<>
to a noop_coroutine_handle
must result in ptr
having a null pointer value — another contradiction between the requirements of noop_coroutine_handle
and coroutine_handle<>
.
[2020-07-12; Reflector prioritization]
Set priority to 2 after reflector discussions.
[2020-07-29 Tim adds PR and comments]
The root cause for this issue as well as issue 3469(i) is the unnecessary public derivation from coroutine_handle<void>
. The proposed resolution below replaces the derivation with a conversion function and adds explicit declarations for members that were previously inherited. It also modifies the preconditions on from_address
with goal of making it impossible to obtain a coroutine_handle<P>
to a coroutine whose promise type is not P
in well-defined code.
[2020-08-21; Issue processing telecon: moved to Tentatively Ready]
[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]
Proposed resolution:
This wording is relative to N4861 and also resolves LWG issue 3469(i).
- Edit 17.13.4 [coroutine.handle] as indicated:
namespace std {
[…]
template
struct coroutine_handle: coroutine_handle<>
{
// [coroutine.handle.con], construct/resetusing coroutine_handle<>::coroutine_handle;
constexpr coroutine_handle() noexcept;
constexpr coroutine_handle(nullptr_t) noexcept;
static coroutine_handle from_promise(Promise&);
coroutine_handle& operator=(nullptr_t) noexcept;// [coroutine.handle.export.import], export/import
constexpr void* address() const noexcept;
static constexpr coroutine_handle from_address(void* addr);// [coroutine.handle.conv], conversion
constexpr operator coroutine_handle<>() const noexcept;// [coroutine.handle.observers], observers
constexpr explicit operator bool() const noexcept;
bool done() const;// [coroutine.handle.resumption], resumption
void operator()() const;
void resume() const;
void destroy() const;// [coroutine.handle.promise], promise access
Promise& promise() const;private:
void* ptr; // exposition only
};
}-1- An object of type
coroutine_handle<T>
is called a _coroutine handle_and can be used to refer to a suspended or executing coroutine. Adefault-constructedcoroutine_handle
object whose memberaddress()
returns a null pointer value does not refer to any coroutine.Twocoroutine_handle
objects refer to the same coroutine if and only if their memberaddress()
returns the same value. - Add the following subclause under 17.13.4 [coroutine.handle], immediately after 17.13.4.2 [coroutine.handle.con]:
?.?.?.? Conversion [coroutine.handle.conv]
constexpr operator coroutine_handle<>() const noexcept;
-1- Effects: Equivalent to:
return coroutine_handle<>::from_address(address());
. - Edit 17.13.4.4 [coroutine.handle.export.import] as indicated, splitting the two versions:
static constexpr coroutine_handle<> coroutine_handle<>::from_address(void* addr);
-?- Preconditions:
addr
was obtained via a prior call toaddress
on an object whose type is a specialization ofcoroutine_handle
.-?- Postconditions:
from_address(address()) == *this
.static constexpr coroutine_handle coroutine_handle::from_address(void* addr);
-2- Preconditions:
addr
was obtained via a prior call toaddress
on an object of type cvcoroutine_handle<Promise>
.-3- Postconditions:
from_address(address()) == *this
. - Edit 17.13.5.2 [coroutine.handle.noop] as indicated:
namespace std {
template<>
struct coroutine_handle: coroutine_handle<>
{
// [coroutine.handle.noop.conv], conversion
constexpr operator coroutine_handle<>() const noexcept;// [coroutine.handle.noop.observers], observers
constexpr explicit operator bool() const noexcept;
constexpr bool done() const noexcept;// [coroutine.handle.noop.resumption], resumption
constexpr void operator()() const noexcept;
constexpr void resume() const noexcept;
constexpr void destroy() const noexcept;// [coroutine.handle.noop.promise], promise access
noop_coroutine_promise& promise() const noexcept;// [coroutine.handle.noop.address], address
constexpr void* address() const noexcept;private:
coroutine_handle(unspecified);
void* ptr; // exposition only
};
} - Add the following subclause under 17.13.5.2 [coroutine.handle.noop], immediately before 17.13.5.2.3 [coroutine.handle.noop.observers]:
?.?.?.?.? Conversion [coroutine.handle.noop.conv]
constexpr operator coroutine_handle<>() const noexcept;
-1- Effects: Equivalent to:
return coroutine_handle<>::from_address(address());
.