Skip to content

Accept rvalue-qualified call types in function - #2371

Open
ispeters wants to merge 2 commits into
NVIDIA:mainfrom
ispeters:accept-rvalue-implicit-objects
Open

ispeters wants to merge 2 commits into
NVIDIA:mainfrom
ispeters:accept-rvalue-implicit-objects

Conversation

@ispeters

Copy link
Copy Markdown
Contributor

function rejects sender_tag(Args...) && and sender_tag(Args...) const &&, on the theory that a function taking its implicit object as an rvalue would borrow an object that's about to expire. But value category is not lifetime: a const & function binds a temporary just as easily, and std::move(obj).get(key) is fine whenever obj outlives the operation. So this diff accepts both forms, optionally noexcept. As for the lvalue forms, the constructor stores a pointer to the object; it takes the object as an rvalue, and the factory receives it as one, so the factory can move from it. A const && function also accepts a non-const rvalue, as a const & one accepts a non-const lvalue.

The __function_meta specializations for member-function call types now all derive from the specialization for the non-member call type with the same return type, parameters and noexcept-ness, adding only the binding of the implicit object, rather than repeating the whole description for each qualifier.

Depends on #2370

@copy-pr-bot

copy-pr-bot Bot commented Oct 27, 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.

ispeters and others added 2 commits October 28, 2026 19:29
A function's constructor takes each curried argument as
`__curried_param_t<Arg>`, which is `Arg const &` or `Arg &&` depending on
whether `Arg` is trivially copy- and move-constructible. Those parameter
types are computed in `__function_meta` and passed to `__function` as
its `_Params` template argument, so merely naming a function evaluates
the traits, which needs every curried parameter's type to be complete.
A class therefore can't name a function that curries an object of the
class in its own definition, e.g. as the return type of a member
function declared there:

    struct base {
      exec::function<ex::sender_tag(base), sigs> copied(this base self);
    };

This diff names functions with curried arguments with a
`__deferred_params` marker in place of the parameter types. The
specialization of `__function` for the marker wraps the specialization
for the computed parameter types, as a `[[no_unique_address]]` member,
and forwards to it, so the types are computed only when the function is
instantiated. A function with no curried arguments has nothing to
compute and keeps naming `__mlist<>` directly.

The wrapper's constructor expands `__curried_param_t<_Args>...` in its
parameter list. That's the shape `_Params` was introduced to avoid,
because nvc++'s name mangler fails on it, but only when the pack is
empty and is followed by a deduced parameter, and the wrapper is never
used with no curried arguments. The member-function form goes back to
expanding the alias too, after its leading `_Self &&` parameter, and
also names the marker, so its curried parameters may also be
incomplete when it's named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`function` rejects `sender_tag(Args...) &&` and
`sender_tag(Args...) const &&`, on the theory that a `function` taking
its implicit object as an rvalue would borrow an object that's about to
expire. But value category is not lifetime: a `const &` function binds
a temporary just as easily, and `std::move(obj).get(key)` is fine
whenever `obj` outlives the operation. So this diff accepts both forms,
optionally `noexcept`. As for the lvalue forms, the constructor stores
a pointer to the object; it takes the object as an rvalue, and the
factory receives it as one, so the factory can move from it. A
`const &&` function also accepts a non-const rvalue, as a `const &` one
accepts a non-const lvalue.

The `__function_meta` specializations for member-function call types
now all derive from the specialization for the non-member call type
with the same return type, parameters and `noexcept`-ness, adding only
the binding of the implicit object, rather than repeating the whole
description for each qualifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ispeters
ispeters force-pushed the accept-rvalue-implicit-objects branch from 916b631 to 9dabacc Compare October 29, 2026 02:30

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.

1 participant