Date: Sun, 08 Jun 2025 13:28:53 +0300
Seastar [1] has seastar::future::get_exception(), and it is quite
important for having reasonable performance during exception storms.
It's not exactly the same, but close enough.
[1] https://github.com/scylladb/seastar
On Sat, 2025-06-07 at 14:48 +0100, Stewart Becker via Std-Proposals
wrote:
> There are times when it would be useful to determine if a std::future
> would throw an exception on calling .get(), but we don't want to
> actually throw an exception. One case is if we have a means to
> continue
> if the future fails to successfully get() a result, e.g. in a
> when_any
> or or_else -like function. In these cases, it is more efficient to
> use
> a a simple branch rather than throwing and catching.
>
> Another case is when throwing and catching exceptions is not
> permitted
> by coding standards. Embedded programming commonly has coding
> standard
> forbidding throw/catch. Yet a std::promise may be fulfilled via
> set_exception, meaning that the try/throw/catch mechanism of
> exception
> handling is not required to put an exception into the shared channel
> between promises and futures - try/catch is only needed on
> std::future's
> end of the channel.
>
> A std::future::get_exception counterpart to
> std::promise::set_exception
> would allow both the existence of an exception, and its nature, to be
> obtained without the overhead of wrapping a try/catch around
> std::future::get(). Invoking get_exception on a future f would be
> defined behaviour if-and-only-if calling f.get() is defined
> behaviour,
> and should return a null std::exception_ptr if-and-only-if f.get()
> doesn't throw. Otherwise it returns a std::exception_ptr wrapping
> the
> exception that f.get() would throw. Calling f.get_exception()
> wouldn't
> cause calling f.get() to become UB, and so get_exception() could be a
> const member function of std::future.
>
>
> *Alternatives*
>
> It might be possible, within a given codebase, to achieve equivalent
> functionality by the use of std::future<std::expected<T,
> exception_ptr>>
> *and* careful definition of the functions used with std::future's
> generators std::async & std::packaged_task. However, this has two
> drawbacks:
>
> 1. std::expected does not support wrapping references - one has to
> wrap
> the reference somehow, leading to more complicated code. This could
> be
> mitigated by having std::expected support references. This idea was
> mentioned (but not proposed) in P2988r0 and anyway was subsequently
> dropped in later revisions of that paper. I am not aware of any
> other
> work in this area.
>
> 2. Regardless, it is still possible for std::future<std::expected<T,
> std::exception_ptr>>::get() to throw. To avoid this, care must be
> taken
> with the functions given to std::future's sources (std::asyc,
> std::packaged_task etc.) to ensure than the exception is supplied
> only
> via a std::unexpected<std::exception_ptr>. Furthermore, the consumer
> of
> the future must know that this is the case. Generic code using this
> approach would still benefit from being able to tell if the
> future.get()
> will throw or not.
>
>
> *Thoughts on implementation*
>
> Regarding implentation, I have looked at the Microsoft, GNU and LLVM
> implementations of std::future and they all use a shared state object
> that has an exception_ptr data member. Indeed, it is difficult to
> imagine how promises and futures could work without such a data
> member
> somewhere. If we allow get_exception() to exhibit UB whenever
> calling
> get() is also UB, then I _think_ that the implementation is simply a
> matter of wait()ing for the future to be ready, then returning this
> data
> member.
>
important for having reasonable performance during exception storms.
It's not exactly the same, but close enough.
[1] https://github.com/scylladb/seastar
On Sat, 2025-06-07 at 14:48 +0100, Stewart Becker via Std-Proposals
wrote:
> There are times when it would be useful to determine if a std::future
> would throw an exception on calling .get(), but we don't want to
> actually throw an exception. One case is if we have a means to
> continue
> if the future fails to successfully get() a result, e.g. in a
> when_any
> or or_else -like function. In these cases, it is more efficient to
> use
> a a simple branch rather than throwing and catching.
>
> Another case is when throwing and catching exceptions is not
> permitted
> by coding standards. Embedded programming commonly has coding
> standard
> forbidding throw/catch. Yet a std::promise may be fulfilled via
> set_exception, meaning that the try/throw/catch mechanism of
> exception
> handling is not required to put an exception into the shared channel
> between promises and futures - try/catch is only needed on
> std::future's
> end of the channel.
>
> A std::future::get_exception counterpart to
> std::promise::set_exception
> would allow both the existence of an exception, and its nature, to be
> obtained without the overhead of wrapping a try/catch around
> std::future::get(). Invoking get_exception on a future f would be
> defined behaviour if-and-only-if calling f.get() is defined
> behaviour,
> and should return a null std::exception_ptr if-and-only-if f.get()
> doesn't throw. Otherwise it returns a std::exception_ptr wrapping
> the
> exception that f.get() would throw. Calling f.get_exception()
> wouldn't
> cause calling f.get() to become UB, and so get_exception() could be a
> const member function of std::future.
>
>
> *Alternatives*
>
> It might be possible, within a given codebase, to achieve equivalent
> functionality by the use of std::future<std::expected<T,
> exception_ptr>>
> *and* careful definition of the functions used with std::future's
> generators std::async & std::packaged_task. However, this has two
> drawbacks:
>
> 1. std::expected does not support wrapping references - one has to
> wrap
> the reference somehow, leading to more complicated code. This could
> be
> mitigated by having std::expected support references. This idea was
> mentioned (but not proposed) in P2988r0 and anyway was subsequently
> dropped in later revisions of that paper. I am not aware of any
> other
> work in this area.
>
> 2. Regardless, it is still possible for std::future<std::expected<T,
> std::exception_ptr>>::get() to throw. To avoid this, care must be
> taken
> with the functions given to std::future's sources (std::asyc,
> std::packaged_task etc.) to ensure than the exception is supplied
> only
> via a std::unexpected<std::exception_ptr>. Furthermore, the consumer
> of
> the future must know that this is the case. Generic code using this
> approach would still benefit from being able to tell if the
> future.get()
> will throw or not.
>
>
> *Thoughts on implementation*
>
> Regarding implentation, I have looked at the Microsoft, GNU and LLVM
> implementations of std::future and they all use a shared state object
> that has an exception_ptr data member. Indeed, it is difficult to
> imagine how promises and futures could work without such a data
> member
> somewhere. If we allow get_exception() to exhibit UB whenever
> calling
> get() is also UB, then I _think_ that the implementation is simply a
> matter of wait()ing for the future to be ready, then returning this
> data
> member.
>
Received on 2025-06-08 10:28:58