Skip to content

Commit 8ee3a91

Browse files
RobertLeahyericniebler
authored andcommitted
stdexec::__apply: Do Not Deduce Return Type
The type returned by stdexec::__apply was previously specified via decltype(auto). This is convenient to write, but means that in order to determine the return type the compiler must substitute into the actual body of the function. Doing this to constexpr functions causes Clang (at least up to 21.1.0) to fail to build the get_env function of many receiver types which are member types of an operation state (and which contain a reference back to that operation state). Consider this example which doesn't build on Clang 21.1.0 and which is intended to be similar to the aforementioned situation: template<typename T> constexpr auto get(T& t) noexcept { return t.get(); } template<typename T> struct state { using return_type = typename T::type; struct inner { constexpr return_type get() const noexcept; state& self; }; static_assert( std::is_same_v< decltype(get(std::declval<inner&>())), int>); T t; }; template<typename T> constexpr auto state<T>::inner::get() const noexcept -> return_type { return self.t.get(); } struct t { using type = int; constexpr int get() const noexcept { return i; } int i; }; constexpr auto impl() noexcept { state<t> s{t{5}}; state<t>::inner i{s}; return get(i); } The error given by clang is that state<t> is incomplete when it's used within state<t>::inner::get. The backtrace associated with the compilation error identifies the static_assert as the problematic source of the use, if it is removed then Clang 21.1.0 accepts the above. This is eyebrow-raising for at least two reasons: - The point of use of state<t> is within the out of line definition of state::inner::get which occurs lexically after state is complete - state::inner::get does not have a deduced return type, therefore the compiler has all the information necessary to determine the return type of get (the free function) without considering the definition of state::inner::get Codifying the second bullet by explicitly specifying the return type of get (the free function): constexpr auto get(T& t) noexcept -> decltype(t.get()) { return t.get(); } Causes Clang 21.1.0 to accept the above example (because it doesn't attempt to build the body of state::inner::get from a context whereat state is incomplete). Note that removing constexpr from state::inner::get causes Clang 21.1.0 to accept the original example. This discussion may seem to have nothing to do with stdexec::__apply until one examines the backtrace generated when Clang fails to build the get_env member function of affected receiver types: The backtrace radiates from building the body of stdexec::__apply. Explicitly specified the return type of stdexec::__apply to ameliorate the above.
1 parent d0096bc commit 8ee3a91

File tree

1 file changed

+6
-4
lines changed

1 file changed

+6
-4
lines changed

include/stdexec/__detail/__tuple.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,10 @@ namespace STDEXEC {
219219
template <class... _Ts>
220220
using __tuple_t = __mcall<_CvRef, __tuple<_Ts...>>;
221221

222-
template <class... _Ts, class... _Us, __callable<_Us..., __mcall<_CvRef, _Ts>...> _Fn>
223-
void operator()(_Fn&& __fn, __tuple_t<_Ts...>&& __tupl, _Us&&... __us) const
224-
noexcept(__nothrow_callable<_Fn, _Us..., __mcall<_CvRef, _Ts>...>);
222+
template <class... _Ts, class... _Us, __callable<_Us..., __mcall1<_CvRef, _Ts>...> _Fn>
223+
auto operator()(_Fn&& __fn, __tuple_t<_Ts...>&& __tupl, _Us&&... __us) const
224+
noexcept(__nothrow_callable<_Fn, _Us..., __mcall1<_CvRef, _Ts>...>)
225+
-> __call_result_t<_Fn, _Us..., __mcall1<_CvRef, _Ts>...>;
225226
};
226227

227228
template <class _Tuple>
@@ -232,7 +233,8 @@ namespace STDEXEC {
232233
requires __callable<__impl_t<_Tuple>, _Fn, _Tuple, _Us...>
233234
STDEXEC_ATTRIBUTE(always_inline, host, device)
234235
constexpr auto operator()(_Fn&& __fn, _Tuple&& __tupl, _Us&&... __us) const
235-
noexcept(__nothrow_callable<__impl_t<_Tuple>, _Fn, _Tuple, _Us...>) -> decltype(auto) {
236+
noexcept(__nothrow_callable<__impl_t<_Tuple>, _Fn, _Tuple, _Us...>)
237+
-> __call_result_t<__impl_t<_Tuple>, _Fn, _Tuple, _Us...> {
236238
constexpr size_t __size = STDEXEC_REMOVE_REFERENCE(_Tuple)::__size;
237239

238240
if constexpr (__size == 0) {

0 commit comments

Comments
 (0)