get_completion_signatures_type - #2194
Conversation
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;
}
ericniebler
left a comment
There was a problem hiding this comment.
we would need a C++26 build in CI with a compiler that supports reflection to actually test this.
|
|
||
| STDEXEC_MODULE_EXPORT | ||
| template <class _Sender, class _Env> | ||
| consteval std::meta::info get_completion_signatures_type(); |
There was a problem hiding this comment.
Since these are non-standard, they should be renamed __get_completion_signatures_type. We can then expose them with non-uglified names in namespace experimental::execution::.
| { | ||
| return STDEXEC_REMOVE_REFERENCE( | ||
| __new_sndr_t)::template get_completion_signatures_type<__new_sndr_t, _Env>(); | ||
| } |
There was a problem hiding this comment.
i think we should also look for __new_sndr_t::get_completion_signatures_type<__new_sndr_t>(). that is, a non-dependent sender may implement get_completion_signatures_type without an Env tparam.
| else | ||
| { | ||
| auto __completions = STDEXEC::get_completion_signatures<_Sender>(); | ||
| return ^^decltype(__completions); |
There was a problem hiding this comment.
i wonder if it would be possible to polyfill the reflection needed here with the friend-injection-based typeid facility in __typeinfo.hpp.
get_completion_signatures attempts to leverage compile-time exceptions to report errors. It does this by:
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:
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:
This doesn't compile because final_sigs isn't a constant expression. If we try to adapt:
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:
Is fragile. It seems as though it could be rewritten as:
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: