3460. Unimplementable noop_coroutine_handle guarantees

Section: 17.12.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.12.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.12.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 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.

  1. Edit 17.12.4 [coroutine.handle] as indicated:

    namespace std {
    
      […]
    
      template<class Promise>
      struct coroutine_handle : coroutine_handle<>
      {
        // [coroutine.handle.con], construct/reset
        using 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. A default-constructed coroutine_­handle object whose member address() returns a null pointer value does not refer to any coroutine. Two coroutine_handle objects refer to the same coroutine if and only if their member address() returns the same value.

  2. Add the following subclause under 17.12.4 [coroutine.handle], immediately after 17.12.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());.

  3. Edit 17.12.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 to address on an object whose type is a specialization of coroutine_handle.

    -?- Postconditions: from_­address(address()) == *this.

    static constexpr coroutine_handle<Promise> coroutine_handle<Promise>::from_address(void* addr);
    

    -2- Preconditions: addr was obtained via a prior call to address on an object of type cv coroutine_handle<Promise>.

    -3- Postconditions: from_­address(address()) == *this.

  4. Edit 17.12.5.2 [coroutine.handle.noop] as indicated:

    namespace std {
      template<>
      struct coroutine_handle<noop_coroutine_promise> : 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 
      };
    }
    
  5. Add the following subclause under 17.12.5.2 [coroutine.handle.noop], immediately before 17.12.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());.