Skip to content

Commit f769180

Browse files
committed
a new implementation for connect that improves diagnostics on failure
1 parent 9ecb671 commit f769180

File tree

5 files changed

+147
-152
lines changed

5 files changed

+147
-152
lines changed

include/stdexec/__detail/__connect.hpp

Lines changed: 108 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
// include these after __execution_fwd.hpp
2121
#include "__completion_signatures_of.hpp"
2222
#include "__connect_awaitable.hpp"
23-
#include "__debug.hpp"
2423
#include "__meta.hpp"
25-
#include "__operation_states.hpp"
2624
#include "__tag_invoke.hpp"
2725
#include "__type_traits.hpp"
2826

@@ -31,148 +29,140 @@ namespace STDEXEC {
3129
// [execution.senders.connect]
3230
namespace __connect {
3331
template <class _Sender, class _Receiver>
34-
using __tfx_sender = __mmemoize_q<transform_sender_result_t, _Sender, env_of_t<_Receiver>>;
32+
using __tfx_sender_t = __mmemoize_q<transform_sender_result_t, _Sender, env_of_t<_Receiver>>;
3533

3634
template <class _Sender, class _Receiver>
37-
using __member_result_t = decltype(__declval<_Sender>().connect(__declval<_Receiver>()));
35+
concept __with_static_member =
36+
requires(__declfn_t<_Sender&&> __sndr, __declfn_t<_Receiver&&> __rcvr) {
37+
STDEXEC_REMOVE_REFERENCE(_Sender)::static_connect(__sndr(), __rcvr());
38+
};
3839

3940
template <class _Sender, class _Receiver>
40-
using __static_member_result_t = //
41-
decltype(STDEXEC_REMOVE_REFERENCE(_Sender) //
42-
::static_connect(__declval<_Sender>(), __declval<_Receiver>()));
41+
concept __with_member = //
42+
requires(__declfn_t<_Sender&&> __sndr, __declfn_t<_Receiver&&> __rcvr) {
43+
__sndr().connect(__rcvr());
44+
};
4345

4446
template <class _Sender, class _Receiver>
45-
concept __with_member = __minvocable_q<__member_result_t, _Sender, _Receiver>;
46-
47-
template <class _Sender, class _Receiver>
48-
concept __with_static_member = __minvocable_q<__static_member_result_t, _Sender, _Receiver>;
47+
concept __with_co_await = __awaitable<_Sender, __connect_await::__promise<_Receiver>>;
4948

5049
template <class _Sender, class _Receiver>
5150
concept __with_legacy_tag_invoke = tag_invocable<connect_t, _Sender, _Receiver>;
5251

5352
template <class _Sender, class _Receiver>
54-
concept __with_co_await = __callable<__connect_awaitable_t, _Sender, _Receiver>;
53+
concept __with_any_connect = __with_static_member<_Sender, _Receiver>
54+
|| __with_member<_Sender, _Receiver>
55+
|| __with_co_await<_Sender, _Receiver>
56+
|| __with_legacy_tag_invoke<_Sender, _Receiver>;
5557

5658
template <class _Sender, class _Receiver>
57-
struct _NO_USABLE_CONNECT_CUSTOMIZATION_FOUND_ {
58-
constexpr void operator()() const noexcept = delete;
59+
concept __connects_to = requires(__declfn_t<_Sender&&> __sndr, __declfn_t<_Receiver&> __rcvr) {
60+
{ transform_sender(__sndr(), get_env(__rcvr())) } -> __with_any_connect<_Receiver>;
5961
};
6062

63+
#define STDEXEC_DECLFN_FOR(_EXPR) __declfn_t<decltype(_EXPR), noexcept(_EXPR)>
64+
65+
// A variable template whose type is a function pointer such that the
66+
// return type and noexcept-ness depend on whether _Sender can be connected
67+
// to _Receiver via any of the supported customization mechanisms.
68+
template <class _Sender, class _Receiver, bool _NothrowTransform>
69+
extern __declfn_t<void> __declfn_v;
70+
71+
template <class _Sender, class _Receiver>
72+
requires __with_static_member<_Sender, _Receiver>
73+
extern STDEXEC_DECLFN_FOR(STDEXEC_REMOVE_REFERENCE(_Sender)::static_connect(
74+
__declval<_Sender>(),
75+
__declval<_Receiver>())) __declfn_v<_Sender, _Receiver, true>;
76+
77+
template <class _Sender, class _Receiver>
78+
requires __with_static_member<_Sender, _Receiver>
79+
extern __declfn_t<
80+
decltype(STDEXEC_REMOVE_REFERENCE(
81+
_Sender)::static_connect(__declval<_Sender>(), __declval<_Receiver>())),
82+
false
83+
>
84+
__declfn_v<_Sender, _Receiver, false>;
85+
86+
template <class _Sender, class _Receiver>
87+
requires __with_static_member<_Sender, _Receiver> //
88+
|| __with_member<_Sender, _Receiver>
89+
extern STDEXEC_DECLFN_FOR(__declval<_Sender>().connect(__declval<_Receiver>()))
90+
__declfn_v<_Sender, _Receiver, true>;
91+
92+
template <class _Sender, class _Receiver>
93+
requires __with_static_member<_Sender, _Receiver> //
94+
|| __with_member<_Sender, _Receiver>
95+
extern __declfn_t<decltype(__declval<_Sender>().connect(__declval<_Receiver>())), false>
96+
__declfn_v<_Sender, _Receiver, false>;
97+
98+
template <class _Sender, class _Receiver>
99+
requires __with_static_member<_Sender, _Receiver> //
100+
|| __with_member<_Sender, _Receiver> //
101+
|| __with_co_await<_Sender, _Receiver>
102+
extern STDEXEC_DECLFN_FOR(__connect_awaitable(__declval<_Sender>(), __declval<_Receiver>()))
103+
__declfn_v<_Sender, _Receiver, true>;
104+
105+
template <class _Sender, class _Receiver>
106+
requires __with_static_member<_Sender, _Receiver> //
107+
|| __with_member<_Sender, _Receiver> //
108+
|| __with_co_await<_Sender, _Receiver>
109+
extern __declfn_t<
110+
decltype(__connect_awaitable(__declval<_Sender>(), __declval<_Receiver>())),
111+
false
112+
>
113+
__declfn_v<_Sender, _Receiver, false>;
114+
115+
template <class _Sender, class _Receiver>
116+
requires __with_static_member<_Sender, _Receiver> //
117+
|| __with_member<_Sender, _Receiver> //
118+
|| __with_co_await<_Sender, _Receiver> //
119+
|| __with_legacy_tag_invoke<_Sender, _Receiver>
120+
extern STDEXEC_DECLFN_FOR(tag_invoke(connect, __declval<_Sender>(), __declval<_Receiver>()))
121+
__declfn_v<_Sender, _Receiver, true>;
122+
123+
template <class _Sender, class _Receiver>
124+
requires __with_static_member<_Sender, _Receiver> //
125+
|| __with_member<_Sender, _Receiver> //
126+
|| __with_co_await<_Sender, _Receiver> //
127+
|| __with_legacy_tag_invoke<_Sender, _Receiver>
128+
extern __declfn_t<tag_invoke_result_t<connect_t, _Sender, _Receiver>, false>
129+
__declfn_v<_Sender, _Receiver, false>;
130+
131+
#undef STDEXEC_DECLFN_FOR
132+
61133
/////////////////////////////////////////////////////////////////////////////
62134
// connect_t
63135
struct connect_t {
64136
template <class _Sender, class _Receiver>
137+
using __declfn_t =
138+
decltype(__declfn_v<
139+
__call_result_t<transform_sender_t, _Sender, env_of_t<_Receiver>>,
140+
_Receiver,
141+
__nothrow_callable<transform_sender_t, _Sender, env_of_t<_Receiver>>
142+
>);
143+
144+
template <class _Sender, class _Receiver, class _DeclFn = __declfn_t<_Sender, _Receiver>>
145+
requires __connects_to<_Sender, _Receiver>
65146
STDEXEC_ATTRIBUTE(always_inline)
66-
static constexpr auto __type_check_arguments() noexcept -> bool {
67-
if constexpr (sender_in<_Sender, env_of_t<_Receiver>>) {
68-
// Instantiate __debug_sender via completion_signatures_of_t to check that the actual
69-
// completions match the expected completions.
70-
using __checked_signatures
71-
[[maybe_unused]] = completion_signatures_of_t<_Sender, env_of_t<_Receiver>>;
72-
} else {
73-
__diagnose_sender_concept_failure<__demangle_t<_Sender>, env_of_t<_Receiver>>();
74-
}
75-
return true;
76-
}
77-
78-
template <class _OpState>
79-
static constexpr void __check_operation_state() noexcept {
80-
static_assert(operation_state<_OpState>, STDEXEC_ERROR_CANNOT_CONNECT_SENDER_TO_RECEIVER);
81-
}
82-
83-
template <class _Sender, class _Receiver>
84-
static consteval auto __get_declfn() noexcept {
85-
static_assert(sender<_Sender>, "The first argument to STDEXEC::connect must be a sender");
86-
if constexpr (!receiver<_Receiver>) {
87-
static_assert(
88-
__nothrow_move_constructible<__decay_t<_Receiver>>,
89-
"Receivers must be nothrow move constructible");
90-
static_assert(
91-
receiver<_Receiver>, "The second argument to STDEXEC::connect must be a receiver");
92-
}
93-
94-
static_assert(sender_in<_Sender, env_of_t<_Receiver>>);
95-
static_assert(__receiver_from<_Receiver, _Sender>);
96-
97-
using _TfxSender = __tfx_sender<_Sender, _Receiver>;
98-
constexpr bool _NothrowTfxSender =
99-
__nothrow_callable<transform_sender_t, _Sender, env_of_t<_Receiver>>;
100-
101-
#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING()
102-
static_assert(__type_check_arguments<_TfxSender, _Receiver>());
103-
#endif
104-
105-
if constexpr (__with_static_member<_TfxSender, _Receiver>) {
106-
using _Result = __static_member_result_t<_TfxSender, _Receiver>;
107-
__check_operation_state<_Result>();
108-
constexpr bool _Nothrow = _NothrowTfxSender
109-
&& noexcept(STDEXEC_REMOVE_REFERENCE(_TfxSender)::static_connect(
110-
__declval<_TfxSender>(), __declval<_Receiver>()));
111-
return __declfn<_Result, _Nothrow>();
112-
} else if constexpr (__with_member<_TfxSender, _Receiver>) {
113-
using _Result = __member_result_t<_TfxSender, _Receiver>;
114-
__check_operation_state<_Result>();
115-
constexpr bool _Nothrow = _NothrowTfxSender
116-
&& noexcept(__declval<_TfxSender>()
117-
.connect(__declval<_Receiver>()));
118-
return __declfn<_Result, _Nothrow>();
119-
} else if constexpr (__with_co_await<_TfxSender, _Receiver>) {
120-
using _Result = __call_result_t<__connect_awaitable_t, _TfxSender, _Receiver>;
121-
return __declfn<_Result, false>();
122-
} else if constexpr (__is_debug_env<env_of_t<_Receiver>>) {
123-
using _Result = __debug::__opstate;
124-
return __declfn<_Result, _NothrowTfxSender>();
125-
} else {
126-
return _NO_USABLE_CONNECT_CUSTOMIZATION_FOUND_<
127-
_WITH_PRETTY_SENDER_<_TfxSender>,
128-
_WITH_RECEIVER_(_Receiver)
129-
>();
130-
}
131-
}
132-
133-
template <class _Sender, class _Receiver, auto _DeclFn = __get_declfn<_Sender, _Receiver>()>
134-
requires __callable<__mtypeof<_DeclFn>>
135147
constexpr auto operator()(_Sender&& __sndr, _Receiver&& __rcvr) const
136-
noexcept(noexcept(_DeclFn())) -> decltype(_DeclFn()) {
137-
using _TfxSender = __tfx_sender<_Sender, _Receiver>;
138-
auto&& __env = get_env(__rcvr);
139-
140-
if constexpr (__with_static_member<_TfxSender, _Receiver>) {
141-
auto&& __tfx_sndr = transform_sender(static_cast<_Sender&&>(__sndr), __env);
142-
return STDEXEC_REMOVE_REFERENCE(_TfxSender)::static_connect(
143-
static_cast<_TfxSender&&>(__tfx_sndr), static_cast<_Receiver&&>(__rcvr));
144-
} else if constexpr (__with_member<_TfxSender, _Receiver>) { // NOLINT(bugprone-branch-clone)
145-
auto&& __tfx_sndr = transform_sender(static_cast<_Sender&&>(__sndr), __env);
146-
return static_cast<_TfxSender&&>(__tfx_sndr).connect(static_cast<_Receiver&&>(__rcvr));
147-
} else if constexpr (__with_co_await<_TfxSender, _Receiver>) {
148-
auto&& __tfx_sndr = transform_sender(static_cast<_Sender&&>(__sndr), __env);
148+
noexcept(__nothrow_callable<_DeclFn>) -> __call_result_t<_DeclFn> {
149+
auto&& __new_sndr = transform_sender(static_cast<_Sender&&>(__sndr), get_env(__rcvr));
150+
using __new_sndr_t = decltype(__new_sndr);
151+
152+
if constexpr (__with_static_member<__new_sndr_t, _Receiver>) {
153+
return STDEXEC_REMOVE_REFERENCE(__new_sndr_t)::static_connect(
154+
static_cast<__new_sndr_t&&>(__new_sndr), static_cast<_Receiver&&>(__rcvr));
155+
} else if constexpr (__with_member<__new_sndr_t, _Receiver>) {
156+
return static_cast<__new_sndr_t&&>(__new_sndr).connect(static_cast<_Receiver&&>(__rcvr));
157+
} else if constexpr (__with_co_await<__new_sndr_t, _Receiver>) {
149158
return __connect_awaitable(
150-
static_cast<_TfxSender&&>(__tfx_sndr), static_cast<_Receiver&&>(__rcvr));
159+
static_cast<__new_sndr_t&&>(__new_sndr), static_cast<_Receiver&&>(__rcvr));
151160
} else {
152-
// This should generate an instantiation backtrace that contains useful
153-
// debugging information.
154-
auto&& __tfx_sndr = transform_sender(static_cast<_Sender&&>(__sndr), __env);
155-
return static_cast<_TfxSender&&>(__tfx_sndr).connect(static_cast<_Receiver&&>(__rcvr));
161+
return tag_invoke(
162+
*this, static_cast<__new_sndr_t&&>(__new_sndr), static_cast<_Receiver&&>(__rcvr));
156163
}
157164
}
158165

159-
template <class _Sender, class _Receiver, auto _DeclFn = __get_declfn<_Sender, _Receiver>()>
160-
requires __callable<__mtypeof<_DeclFn>>
161-
|| __with_legacy_tag_invoke<__tfx_sender<_Sender, _Receiver>, _Receiver>
162-
[[deprecated("the use of tag_invoke for connect is deprecated")]]
163-
constexpr auto operator()(_Sender&& __sndr, _Receiver&& __rcvr) const noexcept(
164-
__nothrow_callable<transform_sender_t, _Sender, env_of_t<_Receiver>>
165-
&& nothrow_tag_invocable<connect_t, __tfx_sender<_Sender, _Receiver>, _Receiver>)
166-
-> tag_invoke_result_t<connect_t, __tfx_sender<_Sender, _Receiver>, _Receiver> {
167-
using _TfxSender = __tfx_sender<_Sender, _Receiver>;
168-
using _Result = tag_invoke_result_t<connect_t, _TfxSender, _Receiver>;
169-
__check_operation_state<_Result>();
170-
auto&& __env = get_env(__rcvr);
171-
auto&& __tfx_sndr = transform_sender(static_cast<_Sender&&>(__sndr), __env);
172-
return tag_invoke(
173-
connect_t(), static_cast<_TfxSender&&>(__tfx_sndr), static_cast<_Receiver&&>(__rcvr));
174-
}
175-
176166
static constexpr auto query(forwarding_query_t) noexcept -> bool {
177167
return false;
178168
}

include/stdexec/__detail/__connect_awaitable.hpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ STDEXEC_PRAGMA_IGNORE_GNU("-Wsubobject-linkage")
3434
namespace STDEXEC {
3535
#if !STDEXEC_NO_STD_COROUTINES()
3636
/////////////////////////////////////////////////////////////////////////////
37-
// __connect_awaitable_
38-
namespace __connect_awaitable_ {
37+
// __connect_await
38+
namespace __connect_await {
3939
struct __promise_base {
4040
constexpr auto initial_suspend() noexcept -> __std::suspend_always {
4141
return {};
@@ -201,10 +201,14 @@ namespace STDEXEC {
201201
return __co_impl(static_cast<_Awaitable&&>(__awaitable), static_cast<_Receiver&&>(__rcvr));
202202
}
203203
};
204-
} // namespace __connect_awaitable_
204+
} // namespace __connect_await
205205

206-
using __connect_awaitable_::__connect_awaitable_t;
206+
using __connect_await::__connect_awaitable_t;
207207
#else
208+
namespace __connect_await {
209+
template <class>
210+
using __promise = void;
211+
} // namespace __connect_await
208212
struct __connect_awaitable_t { };
209213
#endif
210214
inline constexpr __connect_awaitable_t __connect_awaitable{};

0 commit comments

Comments
 (0)