Date: Sat, 7 Jun 2025 15:35:54 +0100
On Sat, 7 Jun 2025 at 15:15, Jonathan Wakely <cxx_at_[hidden]> wrote:
>
>
> On Sat, 7 Jun 2025, 14:48 Stewart Becker via Std-Proposals, <
> std-proposals_at_[hidden]> 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,
>
>
N.B. calling future::get() doesn't necessarily mean that calling it again
is UB. It's specified to change valid() to false, but whether that's UB is
up to the implementation, and recommended practice is to diagnose it by
throwing, so no UB.
> and so get_exception() could be a
>> const member function of std::future.
>>
>
I think what you want to say is that it wouldn't change valid() to false.
Although I'm not sure about that, I don't see why you should only be able
to call future<void>::get() only once (when that doesn't modify the result,
because there isn't one!) but call future<void>::get_exception() more than
once. The main justification I can see is to support code like this that
wants to check for an exception first and if it's not present, get the
result:
auto fut = prom.get_future();
if (auto ex = fut.get_exception())
handleError(exception_ptr_cast<E>(ex));
else
handleResult(fut.get());
but if you could get expected<R,exception_ptr> out then you wouldn't need
that anyway, you would only need to call the function to get the expected
once, then you could test that at leisure:
auto fut = prom.get_future();
if (auto r = fut.get_res())
handleResult(*r);
else
handleError(exception_ptr_cast<E>(r.error()));
This API would also give you all the monadic functions of expected.
prom.get_future().get_result().and_then(handleResult).or_else(handleError);
>
>>
>> *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.
>>
>
> I expect it will come back and we'll get expected<T&,E>, but not as part
> of the same proposal as optional<T&>.
>
> If we get it for C++29 then this point is no longer relevant, since your
> idea couldn't get accepted before C++29 anyway.
>
>
>
>> 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.
>>
>
>
> Right, but I still think we could improve your program by using expected.
>
Sorry, phone autocorrect turned "proposal" into "program".
>
> What if instead of get() and get_exception() we had get() and get_result()
> where the latter returns expected<R,exception_ptr>?
>
>
>
>
> On Sat, 7 Jun 2025, 14:48 Stewart Becker via Std-Proposals, <
> std-proposals_at_[hidden]> 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,
>
>
N.B. calling future::get() doesn't necessarily mean that calling it again
is UB. It's specified to change valid() to false, but whether that's UB is
up to the implementation, and recommended practice is to diagnose it by
throwing, so no UB.
> and so get_exception() could be a
>> const member function of std::future.
>>
>
I think what you want to say is that it wouldn't change valid() to false.
Although I'm not sure about that, I don't see why you should only be able
to call future<void>::get() only once (when that doesn't modify the result,
because there isn't one!) but call future<void>::get_exception() more than
once. The main justification I can see is to support code like this that
wants to check for an exception first and if it's not present, get the
result:
auto fut = prom.get_future();
if (auto ex = fut.get_exception())
handleError(exception_ptr_cast<E>(ex));
else
handleResult(fut.get());
but if you could get expected<R,exception_ptr> out then you wouldn't need
that anyway, you would only need to call the function to get the expected
once, then you could test that at leisure:
auto fut = prom.get_future();
if (auto r = fut.get_res())
handleResult(*r);
else
handleError(exception_ptr_cast<E>(r.error()));
This API would also give you all the monadic functions of expected.
prom.get_future().get_result().and_then(handleResult).or_else(handleError);
>
>>
>> *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.
>>
>
> I expect it will come back and we'll get expected<T&,E>, but not as part
> of the same proposal as optional<T&>.
>
> If we get it for C++29 then this point is no longer relevant, since your
> idea couldn't get accepted before C++29 anyway.
>
>
>
>> 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.
>>
>
>
> Right, but I still think we could improve your program by using expected.
>
Sorry, phone autocorrect turned "proposal" into "program".
>
> What if instead of get() and get_exception() we had get() and get_result()
> where the latter returns expected<R,exception_ptr>?
>
>
Received on 2025-06-07 14:36:15