Skip to content

exec(fork_join): coalesce empty and unary calls - #2302

Open
romintomasetti wants to merge 2 commits into
NVIDIA:mainfrom
romintomasetti:fork-join-p4269
Open

romintomasetti wants to merge 2 commits into
NVIDIA:mainfrom
romintomasetti:fork-join-p4269

Conversation

@romintomasetti

@romintomasetti romintomasetti commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
  • When exec::fork_join(sndr, clsr) is given a single closure, do no attempt to wrap it in the (thereby useless) fork_join machinery. Return sndr | clsr.
  • When exec::fork_join(sndr) is given no closure, it behaves like sndr.

As a drive-by, I've added a few noexcept to then.

Similar to:

See P4269R1.

@copy-pr-bot

copy-pr-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown

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.

@romintomasetti

Copy link
Copy Markdown
Contributor Author

@RobertLeahy @ericniebler Could you please have a look? Thanks!

Comment thread include/exec/fork_join.hpp Outdated

/// Unary closure.
template <STDEXEC::sender Sndr, class Closure> requires (!STDEXEC::sender<Closure>)
constexpr auto operator()(Sndr&& sndr, Closure&& clsr) const noexcept(STDEXEC::__nothrow_decay_copyable<Closure>)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this noexcept is correct. Just because Closure can be decay-copied doesn't mean binding it together with something of type Sndr can't.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! How would you do it, apart from

noexcept(noexcept(static_cast<Sndr&&>(sndr) | static_cast<Closure&&>(clsr)))

?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use inspiration from include/stdexec/__detail/__sender_adaptor_closure.hpp?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. What do you think?

Comment thread include/exec/fork_join.hpp
Comment thread include/exec/fork_join.hpp Outdated
Signed-off-by: romintomasetti <romin.tomasetti@gmail.com>
Comment thread include/exec/fork_join.hpp Outdated
Comment thread test/stdexec/algos/adaptors/test_then.cpp
Comment thread test/exec/test_fork_join.cpp Outdated
Comment thread include/stdexec/__detail/__then.hpp
/// Multiple closures.
auto multiple = STDEXEC::just() | exec::fork_join(then, then);
STATIC_REQUIRE(exec::sender_for<decltype(multiple), exec::fork_join_t>);
STATIC_REQUIRE(!noexcept(exec::fork_join(STDEXEC::just(), then, then)));

@maartenarnst maartenarnst Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also omit this line. Because it seems the reason is that the function has no noexcept clause, rather than that one operations may throw.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but at least it's tested.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect constructing such an exec::fork_join sender not to throw, which noexcept is missing that prevents that?

@romintomasetti romintomasetti Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 functions in the fork_join_t struct I haven't touched yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we add tests locking in broken/incorrect/inaccurate behavior?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My two cents would be to add the noexcept clause and flip the static_assert here.

We could write something like (perhaps there's a way to make it less verbose with other concepts from __concepts.hpp):

noexcept(STDEXEC::__nothrow_decay_copyable<Closures..., Sndr>
         && (std::is_nothrow_move_constructible_v<STDEXEC::__decay_t<Closures>> && ...)
         && std::is_nothrow_move_constructible_v<STDEXEC::__decay_t<Sndr>>)

The last two checks would be needed for the moves here:

template <class _Tag>
struct __make_sexpr_t
{
template <class _Data = __, class... _Child>
constexpr auto operator()(_Data __data = {}, _Child... __child) const
{
return __sexpr_t<_Tag, _Data, _Child...>{
{_Tag(), static_cast<_Data&&>(__data), static_cast<_Child&&>(__child)...}
};
}
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericniebler What do you think?

Similar to NVIDIA#2124. See  P4269R1.

Signed-off-by: romintomasetti <romin.tomasetti@gmail.com>
template <sender _Sender, __movable_value _Fun>
constexpr auto operator()(_Sender&& __sndr, _Fun __fun) const -> __well_formed_sender auto
constexpr auto operator()(_Sender&& __sndr, _Fun __fun) const
noexcept(__nothrow_decay_copyable<_Fun, _Sender>)

@maartenarnst maartenarnst Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to add std::is_nothrow_move_constructible_v<STDEXEC::__decay_t<_Sender>> here too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this would be a departure from the rest of the code base, that usually just checks __nothrow_decay_copyable_t

{
template <class... _Senders>
[[nodiscard]]
constexpr auto operator()(_Senders &&...__sndrs) const //
noexcept(__nothrow_decay_copyable<_Senders...>) -> __well_formed_sender auto
{

This branch has not been deployed

No deployments
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.

3 participants