4158. packaged_task::operator= should abandon its shared state

Section: 32.10.10.2 [futures.task.members] Status: New Submitter: Jonathan Wakely Opened: 2024-09-19 Last modified: 2024-10-03

Priority: 3

View other active issues in [futures.task.members].

View all other issues in [futures.task.members].

View all issues with New status.

Discussion:

The packaged_task move assignment operator is specified to release the previous shared state. This means it releases ownership, but does not make the shared state ready. Any future that shares ownership of the shared state will never receive a result, because the provider is gone. This means that any thread that waits on the future will block forever.

There is a note on packaged_task::reset() which claims that assignment abandons the state, which is not supported by any normative wording:

void reset();

-26- Effects: As if *this = packaged_task(std::move(f)), where f is the task stored in *this.

[Note 2: This constructs a new shared state for *this. The old state is abandoned (32.10.5 [futures.state]). — end note]

Presumably, the intended behaviour of assignment was to abandon the shared state, i.e. make it ready with a broken_promise error, and then release it. That is what the std::promise move assignment does (see 32.10.6 [futures.promise] p9). Both libstdc++ and libc++ abandon the state, despite what the standard says.

[2024-10-02; Reflector poll]

Set priority to 3 after reflector poll.

Previous resolution [SUPERSEDED]:

This wording is relative to N4988.

  1. Modify 32.10.10.2 [futures.task.members] as indicated:

    packaged_task& operator=(packaged_task&& rhs) noexcept;
    

    -11- Effects:

    1. (11.1) — Releases Abandons any shared state (32.10.5 [futures.state]);
    2. (11.2) — calls packaged_task(std::move(rhs)).swap(*this).

    -?- Returns: *this.

[2024-10-02; Jonathan provides improved wording]

Following reflector discussion, remove the "Releases any shared state" text completely. The remaining effects imply that the state will be abandoned anyway. This makes it safe against self-assignment.

[2024-10-02; LWG telecon]

Agreed to change promise the same way, which is safe for self-assignment and matches what all three of libstdc++, libc++ and MSVC do today. Ask SG1 to review.

Proposed resolution:

This wording is relative to N4988.

  1. Modify 32.10.6 [futures.promise] as indicated:

    promise& operator=(promise&& rhs) noexcept;
    

    -9- Effects: Abandons any shared state (32.10.5 [futures.state]) and then as if Equivalent to promise(std::move(rhs)).swap(*this).

    -10- Returns: *this.

  2. Modify 32.10.10.2 [futures.task.members] as indicated:

    packaged_task& operator=(packaged_task&& rhs) noexcept;
    

    -11- Effects:

    1. (11.1) — Releases any shared state (32.10.5 [futures.state]);
    2. (11.2) — calls Equivalent to packaged_task(std::move(rhs)).swap(*this).

    -?- Returns: *this.