Skip to content

get_completion_signatures_type - #2194

Draft
RobertLeahy wants to merge 1 commit into
NVIDIA:mainfrom
RobertLeahy:get_completion_signatures_type_20260812
Draft

get_completion_signatures_type#2194
RobertLeahy wants to merge 1 commit into
NVIDIA:mainfrom
RobertLeahy:get_completion_signatures_type_20260812

Conversation

@RobertLeahy

Copy link
Copy Markdown
Contributor

get_completion_signatures attempts to leverage compile-time exceptions to report errors. It does this by:

  • Throwing an exception when it cannot synthesize completion signatures (definitionally this happens at compile time because it's consteval), and
  • Reporting that it returns completion_signatures<> in situations wherein it would throw an exception

The latter is reflective of a tension: get_completion_signatures "knows" it will throw an exception when the return type is computed, but that exception is not surfaced until and unless the function is invoked. This creates the opportunity for very interesting bugs, for example decltype( get_completion_signatures<...>()) is, in a vacuum, likely to be a bug because this doesn't actually evaluate the expression and therefore any exception is not thrown.

The solution is complete surrender to the invocation of functions at compile time (rather than exposing information via channels which can be interrogated via an unevaluated context): Reflection.

Added get_completion_signatures_type which returns std::meta::info and provides a reflection-first way to access completion signatures computation. In addition to ameliorating the above-described tension in the design of get_completion_signatures this new function allows for better ergonomics in reflection-first uses.

When attempting to use reflection to aid in the computation of a certain asynchronous operation's completion signatures one needs to, in general:

  • Obtain the completion signatures of one or more child operations
  • Examine and manipulate those completion signatures
  • Yield the final completion signatures

With get_completion_signatures this becomes awkward. The way to "lift" the completion signatures into the reflection domain is via ^^decltype( get_completion_signatures<...>()), which is pathological as discussed above. In order to expose the needed interface (i.e. a static, consteval member function get_completion_signatures which returns an instance of an instantiation of the completion_signatures class template) one needs to splice a reflection, but one can't splice a reflection unless that reflection is constexpr, but initialization of constexpr variables needs to be done by a constant expression which can't propagate exceptions thereby ruining the entire error reporting mechanism of get_completion_signatures.

To make the above concrete consider:

  template<typename Self, typename Env>
  static consteval auto get_completion_signatures() {
    auto sigs = get_completion_signatures<...>();
    auto meta = ^^decltype(sigs);
    auto final_sigs = /* depends on meta */;
    return typename [:final_sigs:]{};
  }

This doesn't compile because final_sigs isn't a constant expression. If we try to adapt:

  template<typename Self, typename Env>
  static consteval auto get_completion_signatures() {
    auto sigs = get_completion_signatures<...>();
    constexpr auto meta = ^^decltype(sigs);
    constexpr auto final_sigs = /* depends on meta */;
    return typename [:final_sigs:]{};
  }

Then any operation in the synthesis of final_sigs which throws an exception fails compilation rather than being propagated. Intuitively we can understand why this is the case: The return type of the function depends thereupon, a problem get_completion_signatures works around by coalescing to the dummy type completion_signatures<>.

Also note again that in both of the above examples:

  auto sigs = get_completion_signatures<...>();
  constexpr auto meta = ^^decltype(sigs);

Is fragile. It seems as though it could be rewritten as:

  constexpr auto meta = ^^decltype(get_completion_signatures<...>());

But that would introduce a subtle bug: get_completion_signatures would not actually be evaluated.

get_completion_signatures_type ameliorates the above. The problematic member function can be written as:

  template<typename Self, typename Env>
  static consteval std::meta::info get_completion_signatures_type() {
    auto meta = get_completion_signatures_type<...>();
    auto final_sigs = /* depends on meta */;
    return final_sigs;
  }

get_completion_signatures attempts to leverage compile-time exceptions
to report errors. It does this by:

- Throwing an exception when it cannot synthesize completion signatures
  (definitionally this happens at compile time because it's consteval),
  and
- Reporting that it returns completion_signatures<> in situations
  wherein it would throw an exception

The latter is reflective of a tension: get_completion_signatures "knows"
it will throw an exception when the return type is computed, but that
exception is not surfaced until and unless the function is invoked. This
creates the opportunity for very interesting bugs, for example decltype(
get_completion_signatures<...>()) is, in a vacuum, likely to be a bug
because this doesn't actually evaluate the expression and therefore any
exception is not thrown.

The solution is complete surrender to the invocation of functions at
compile time (rather than exposing information via channels which can be
interrogated via an unevaluated context): Reflection.

Added get_completion_signatures_type which returns std::meta::info and
provides a reflection-first way to access completion signatures
computation. In addition to ameliorating the above-described tension in
the design of get_completion_signatures this new function allows for
better ergonomics in reflection-first uses.

When attempting to use reflection to aid in the computation of a certain
asynchronous operation's completion signatures one needs to, in general:

- Obtain the completion signatures of one or more child operations
- Examine and manipulate those completion signatures
- Yield the final completion signatures

With get_completion_signatures this becomes awkward. The way to "lift"
the completion signatures into the reflection domain is via ^^decltype(
get_completion_signatures<...>()), which is pathological as discussed
above. In order to expose the needed interface (i.e. a static, consteval
member function get_completion_signatures which returns an instance of
an instantiation of the completion_signatures class template) one needs
to splice a reflection, but one can't splice a reflection unless that
reflection is constexpr, but initialization of constexpr variables needs
to be done by a constant expression which can't propagate exceptions
thereby ruining the entire error reporting mechanism of
get_completion_signatures.

To make the above concrete consider:

  template<typename Self, typename Env>
  static consteval auto get_completion_signatures() {
    auto sigs = get_completion_signatures<...>();
    auto meta = ^^decltype(sigs);
    auto final_sigs = /* depends on meta */;
    return typename [:final_sigs:]{};
  }

This doesn't compile because final_sigs isn't a constant expression. If
we try to adapt:

  template<typename Self, typename Env>
  static consteval auto get_completion_signatures() {
    auto sigs = get_completion_signatures<...>();
    constexpr auto meta = ^^decltype(sigs);
    constexpr auto final_sigs = /* depends on meta */;
    return typename [:final_sigs:]{};
  }

Then any operation in the synthesis of final_sigs which throws an
exception fails compilation rather than being propagated. Intuitively we
can understand why this is the case: The return type of the function
depends thereupon, a problem get_completion_signatures works around by
coalescing to the dummy type completion_signatures<>.

Also note again that in both of the above examples:

  auto sigs = get_completion_signatures<...>();
  constexpr auto meta = ^^decltype(sigs);

Is fragile. It seems as though it could be rewritten as:

  constexpr auto meta = ^^decltype(get_completion_signatures<...>());

But that would introduce a subtle bug: get_completion_signatures would
not actually be evaluated.

get_completion_signatures_type ameliorates the above. The problematic
member function can be written as:

  template<typename Self, typename Env>
  static consteval std::meta::info get_completion_signatures_type() {
    auto meta = get_completion_signatures_type<...>();
    auto final_sigs = /* depends on meta */;
    return final_sigs;
  }
@copy-pr-bot

copy-pr-bot Bot commented Aug 12, 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.

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