C++ Logo

std-proposals

Advanced search

[std-proposals] std::future::get_exception()

From: Stewart Becker <stewart_at_[hidden]>
Date: Sat, 7 Jun 2025 14:48:22 +0100
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-07 13:48:25