This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

### 3582. Unclear where `std::async` exceptions are handled

Section: 33.10.9 [futures.async] Status: New Submitter: Jonathan Wakely Opened: 2021-08-23 Last modified: 2021-09-30

Priority: 3

View other active issues in [futures.async].

View all other issues in [futures.async].

View all issues with New status.

Discussion:

33.10.9 [futures.async] (3.1) says:

> Any exception propagated from the execution of `invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)` is stored as the exceptional result in the shared state.

It's not clear whether this includes the evaluation of the `decay-copy` calls in the calling thread, or only the invocation of invoke with the results of those `decay-copy` calls.

A literal reading suggests that any exceptions from any part of that expression should be stored in the shared state. All of libstdc++, libc++ and MSVC only store exceptions from the call to `invoke`, not the calls to `decay-copy`. Exceptions from the `decay-copy` calls are propagated to the caller of `std::async`. We should clarify that that's what the standard means.

[2021-09-20; Reflector poll]

Set priority to 3 after reflector poll.

[2021-09-20; Jonathan updates wording to change the Throws: and attempt to align the Effects: with the deferred function case. ]

Previous resolution [SUPERSEDED]:

> This wording is relative to N4892.

> 1. Modify 33.10.9 [futures.async] as indicated:

>    >     template<class F, class... Args>
>    >       [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
>    >         async(F&& f, Args&&... args);
>    >     template<class F, class... Args>
>    >       [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
>    >         async(launch policy, F&& f, Args&&... args);

>    > > -2- Mandates: […]

>    > > -3- Effects: The first function behaves the same as a call to the second function with a `policy` argument of `launch::async | launch::deferred` and the same arguments for `F` and `Args`. The second function creates a shared state that is associated with the returned `future` object. The further behavior of the second function depends on the `policy` argument as follows (if more than one of these conditions applies, the implementation may choose any of the corresponding policies):

>    > > 1. (3.1) — If `launch::async` is set in `policy`, calls `invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)` (22.10.4 [func.require], 33.4.3.3 [thread.thread.constr]) as if in a new thread of execution represented by a `thread` object with the calls to `decay-copy` being evaluated in the thread that called `async`. Any return value is stored as the result in the shared state. Any exception propagated from the execution of `invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)`call to `invoke` is stored as the exceptional result in the shared state. [Note ?: Exceptions from the `decay-copy` calls are propagated to the caller. — end note] The `thread` object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.

>    > > 2. […]

Proposed resolution:

This wording is relative to N4892.

1. Modify 33.10.9 [futures.async] as indicated:

   >     template<class F, class... Args>
   >       [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
   >         async(F&& f, Args&&... args);
   >     template<class F, class... Args>
   >       [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
   >         async(launch policy, F&& f, Args&&... args);

   > > -2- Mandates: […]

   > > -3- Effects: The first function behaves the same as a call to the second function with a `policy` argument of `launch::async | launch::deferred` and the same arguments for `F` and `Args`. The second function creates a shared state that is associated with the returned `future` object. The further behavior of the second function depends on the `policy` argument as follows (if more than one of these conditions applies, the implementation may choose any of the corresponding policies):

   > > 1. (3.1) — If `launch::async` is set in `policy`, calls `invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)` (22.10.4 [func.require], 33.4.3.3 [thread.thread.constr]) as if in a new thread of execution represented by a `thread` object with the calls to `decay-copy` being evaluated in the thread that called `async`. Any return value is stored as the result in the shared state. Any exception propagated from the execution of `invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args)...)std::move(g), std::move(xyz))` is stored as the exceptional result in the shared state, where `g` is the result of `decay-copy(std::forward<F>(f))` and `xyz` is the result of `decay-copy(std::forward<Args>(args))...` . [Note ?: Exceptions from the `decay-copy` calls are propagated to the caller. — end note] The `thread` object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.

   > > 2. […]

   > > […]

   > > -6- Throws: `system_error` if `policy == launch::async` and the implementation is unable to start a new thread; `std::bad_alloc` if memory for the internal data structures cannot be allocated; or any exception thrown by the initialization of the objects returned by the `decay-copy` calls.