Skip to content

let_value, _error, & _stopped: Check Invocation Noexceptness With Cor…#1712

Merged
ericniebler merged 1 commit intoNVIDIA:mainfrom
RobertLeahy:let_value_invocation_noexcept_20251213
Dec 15, 2025
Merged

let_value, _error, & _stopped: Check Invocation Noexceptness With Cor…#1712
ericniebler merged 1 commit intoNVIDIA:mainfrom
RobertLeahy:let_value_invocation_noexcept_20251213

Conversation

@RobertLeahy
Copy link
Contributor

…rect Value Category

After the delivery of a completion signal which ought to be intercepted let_value, let_error, and let_stopped perform a "bind" operation. The intercepted values (if any) are stored within the operation state and then the invocable used to parameterize the operation is invoked to obtain a new sender which is then connected and started. If it's the case that:

  • Decay-copying the intercepted values does not throw,
  • Invoking the invocable does not throw, and
  • Connecting the obtained sender does not throw

Then the bind operation doesn't throw. In this case there's no need to wrap that operation in a try/catch and emit the emission of an error completion signal from the catch block.

Prior to this commit when checking the second bullet above the implementations of let_value, let_error, and let_stopped checked for invocability with the value category by which the values were provided by the predecessor operation. The invocation actually takes place with the stored, decay-copied versions of these values. This meant that if the provided invocable had multiple overloads of its function call operator with different noexcept specifications the implementation could proceed as if no exception could be thrown when this was not the case leading to an exception propagating into noexcept and std::terminate being called.

Reproduced in a unit test and fixed.

…rect Value Category

After the delivery of a completion signal which ought to be intercepted
let_value, let_error, and let_stopped perform a "bind" operation. The
intercepted values (if any) are stored within the operation state and
then the invocable used to parameterize the operation is invoked to
obtain a new sender which is then connected and started. If it's the
case that:

- Decay-copying the intercepted values does not throw,
- Invoking the invocable does not throw, and
- Connecting the obtained sender does not throw

Then the bind operation doesn't throw. In this case there's no need to
wrap that operation in a try/catch and emit the emission of an error
completion signal from the catch block.

Prior to this commit when checking the second bullet above the
implementations of let_value, let_error, and let_stopped checked for
invocability with the value category by which the values were provided
by the predecessor operation. The invocation actually takes place with
the stored, decay-copied versions of these values. This meant that if
the provided invocable had multiple overloads of its function call
operator with different noexcept specifications the implementation could
proceed as if no exception could be thrown when this was not the case
leading to an exception propagating into noexcept and std::terminate
being called.

Reproduced in a unit test and fixed.
@copy-pr-bot
Copy link

copy-pr-bot bot commented Dec 13, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ericniebler
Copy link
Collaborator

/ok to test ef03f7b

@ericniebler ericniebler merged commit 2979af5 into NVIDIA:main Dec 15, 2025
21 checks passed
@ericniebler
Copy link
Collaborator

nice find. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants