Skip to content

make sync_wait hard error instead of SFINAE when passed a bad sender #1504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmake/clangd_compile_info.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Symlink the compile command output to the source dir, where clangd will find it.
set(compile_commands_file "${CMAKE_BINARY_DIR}/compile_commands.json")
set(compile_commands_link "${CMAKE_SOURCE_DIR}/compile_commands.json")
message(STATUS "Creating symlink from ${compile_commands_link} to ${compile_commands_file}...")
message(STATUS "Creating symlink from \"${compile_commands_file}\" to \"${compile_commands_link}\"...")
stdexec_execute_non_fatal_process(COMMAND
"${CMAKE_COMMAND}" -E rm -f "${compile_commands_link}")
stdexec_execute_non_fatal_process(COMMAND
Expand Down
2 changes: 1 addition & 1 deletion include/stdexec/__detail/__basic_sender.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ namespace stdexec {
};

static constexpr auto get_completion_signatures = //
[]<class _Sender>(_Sender&& __sndr, auto&&...) noexcept {
[]<class _Sender>(_Sender&&, auto&&...) noexcept {
static_assert(
__mnever<tag_of_t<_Sender>>,
"No customization of get_completion_signatures for this sender tag type.");
Expand Down
4 changes: 2 additions & 2 deletions include/stdexec/__detail/__completion_signatures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace stdexec {
concept __valid_completion_signatures = //
__same_as<__ok_t<_Completions>, __msuccess> && __sigs::__is_completion_signatures<_Completions>;

template <class _Sender, class _Env>
template <class _Sender, class... _Env>
using __unrecognized_sender_error = //
__mexception<_UNRECOGNIZED_SENDER_TYPE_<>, _WITH_SENDER_<_Sender>, _WITH_ENVIRONMENT_<_Env>>;
__mexception<_UNRECOGNIZED_SENDER_TYPE_<>, _WITH_SENDER_<_Sender>, _WITH_ENVIRONMENT_<_Env>...>;
} // namespace stdexec
2 changes: 1 addition & 1 deletion include/stdexec/__detail/__continues_on.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ namespace stdexec {
}

template <class _Sender, class _Env>
static auto transform_sender(_Sender&& __sndr, const _Env& __env) {
static auto transform_sender(_Sender&& __sndr, const _Env&) {
return __sexpr_apply(static_cast<_Sender&&>(__sndr), __transform_sender_fn());
}
};
Expand Down
18 changes: 2 additions & 16 deletions include/stdexec/__detail/__env.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
#include "__tag_invoke.hpp"
#include "__tuple.hpp"

#include <type_traits>
#include <functional> // IWYU pragma: keep for unwrap_reference_t
#include <type_traits>
#include <utility>

STDEXEC_PRAGMA_PUSH()
Expand Down Expand Up @@ -211,7 +211,7 @@ namespace stdexec {
struct get_domain_t {
template <class _Ty>
requires tag_invocable<get_domain_t, const _Ty&>
constexpr auto operator()(const _Ty& __ty) const noexcept
constexpr auto operator()(const _Ty&) const noexcept
-> __decay_t<tag_invoke_result_t<get_domain_t, const _Ty&>> {
static_assert(
nothrow_tag_invocable<get_domain_t, const _Ty&>,
Expand Down Expand Up @@ -361,8 +361,6 @@ namespace stdexec {
STDEXEC_ATTRIBUTE((nodiscard)) constexpr auto query(_Query) const noexcept -> const _Value& {
return __value;
}

auto operator=(const prop&) -> prop& = delete;
};

template <class _Query, class _Value>
Expand Down Expand Up @@ -398,8 +396,6 @@ namespace stdexec {
return tag_invoke(
__q, env::__get_1st<_Query, _Args...>(*this), static_cast<_Args&&>(__args)...);
}

auto operator=(const env&) -> env& = delete;
};

// specialization for two envs to avoid warnings about elided braces
Expand Down Expand Up @@ -433,8 +429,6 @@ namespace stdexec {
return tag_invoke(
__q, env::__get_1st<_Query, _Args...>(*this), static_cast<_Args&&>(__args)...);
}

auto operator=(const env&) -> env& = delete;
};

template <class... _Envs>
Expand All @@ -461,8 +455,6 @@ namespace stdexec {
auto query(_Key) const noexcept -> const _Value& {
return __value_;
}

auto operator=(const __with&) -> __with& = delete;
};

template <class _Value, class _Tag, class... _Tags>
Expand Down Expand Up @@ -494,8 +486,6 @@ namespace stdexec {
-> tag_invoke_result_t<_Tag, __cvref_env_t> {
return tag_invoke(_Tag(), __env_);
}

auto operator=(const __t&) -> __t& = delete;
};
};

Expand Down Expand Up @@ -539,8 +529,6 @@ namespace stdexec {
query(_Key) const noexcept(nothrow_tag_invocable<_Key, __cvref_env_t>) -> decltype(auto) {
return tag_invoke(_Key(), __env_);
}

auto operator=(const __t&) -> __t& = delete;
};
};

Expand Down Expand Up @@ -573,8 +561,6 @@ namespace stdexec {
-> __call_result_t<const _Fun&, _Tag> {
return __fun_(_Tag());
}

auto operator=(const __from&) -> __from& = delete;
};

template <class _Fun>
Expand Down
51 changes: 51 additions & 0 deletions include/stdexec/__detail/__senders.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "__transform_completion_signatures.hpp"
#include "__transform_sender.hpp"
#include "__type_traits.hpp"
#include <type_traits>

namespace stdexec {
/////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -335,4 +336,54 @@ namespace stdexec {
template <class _Sender>
concept __well_formed_sender = __detail::__well_formed_sender<
__minvoke<__with_default_q<__completion_signatures_of_t, dependent_completions>, _Sender>>;

// Used to report a meaningful error message when the sender_in<Sndr, Env> concept check fails.
template <class _Sender, class... _Env>
auto __diagnose_sender_concept_failure() {
if constexpr (!enable_sender<_Sender>) {
static_assert(
enable_sender<_Sender>,
"The given type is not a sender because stdexec::enable_sender<Sender> is false. Either "
"give the type a nested ::sender_concept typedef that is an alias for stdexec::sender_t, "
"or else specialize the stdexec::enable_sender boolean trait for this type to true.");
} else if constexpr (!__detail::__consistent_completion_domains<_Sender>) {
static_assert(
__detail::__consistent_completion_domains<_Sender>,
"The completion schedulers of the sender do not have consistent domains. This is likely a "
"bug in the sender implementation.");
} else if constexpr (!move_constructible<__decay_t<_Sender>>) {
static_assert(
move_constructible<__decay_t<_Sender>>, //
"The sender type is not move-constructible.");
} else if constexpr (!constructible_from<__decay_t<_Sender>, _Sender>) {
static_assert(
constructible_from<__decay_t<_Sender>, _Sender>,
"The sender cannot be decay-copied. Did you forget a std::move?");
} else {
using _Completions = __completion_signatures_of_t<_Sender, _Env...>;
if constexpr (__same_as<_Completions, __unrecognized_sender_error<_Sender, _Env...>>) {
static_assert(
__mnever<_Completions>,
"The sender type was not able to report its completion signatures when asked. This is "
"either because it lacks the necessary member functions, or because the member functions "
"were ill-formed.\n\nA sender can declare its completion signatures in one of two ways:\n"
" 1. By defining a nested type alias named `completion_signatures` that is a\n"
" specialization of stdexec::completion_signatures<...>.\n"
" 2. By defining a member function named `get_completion_signatures` that returns a\n"
" specialization of stdexec::completion_signatures<...>.");
} else if constexpr (__merror<_Completions>) {
static_assert(
!__merror<_Completions>,
"Trying to compute the sender's completion signatures resulted in an error. See the rest "
"of the compiler diagnostic for clues. Look for the string \"_ERROR_\".");
// MSVC needs more encouragement to print the type of the error.
STDEXEC_MSVC(_Completions __what = 0;)
} else {
static_assert(
__valid_completion_signatures<_Completions>,
"The stdexec::sender_in<Sender, Environment> concept check has failed. This is likely a "
"bug in the sender implementation.");
}
}
}
} // namespace stdexec
94 changes: 50 additions & 44 deletions include/stdexec/__detail/__sync_wait.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,55 +178,61 @@ namespace stdexec {
_Sender,
__env>>;

#if STDEXEC_EDG()
// It requires some hoop-jumping to get the NVHPC compiler to report a meaningful
// diagnostic for SFINAE failures.
template <class _Sender>
auto __diagnose_error() {
if constexpr (!sender_in<_Sender, __env>) {
using _Completions = __completion_signatures_of_t<_Sender, __env>;
if constexpr (__merror<_Completions>) {
return _Completions();
} else {
constexpr __mstring __diag =
"The stdexec::sender_in<Sender, Environment> concept check has failed."_mstr;
return __sync_wait_error<__diag, _Sender>();
}
} else if constexpr (!__valid_sync_wait_argument<_Sender>) {
return __sync_wait_error<__too_many_successful_completions_diag, _Sender>();
} else if constexpr (!sender_to<_Sender, __sync_receiver_for_t<_Sender>>) {
constexpr __mstring __diag =
"Failed to connect the given sender to sync_wait's internal receiver. "
"The stdexec::connect(Sender, Receiver) expression is ill-formed."_mstr;
return __sync_wait_error<__diag, _Sender>();
} else {
constexpr __mstring __diag = "Unknown concept check failure."_mstr;
return __sync_wait_error<__diag, _Sender>();
}
}

template <class _Sender>
using __error_description_t = decltype(__sync_wait::__diagnose_error<_Sender>());
#endif

////////////////////////////////////////////////////////////////////////////
// [execution.senders.consumers.sync_wait]
struct sync_wait_t {
template <sender_in<__env> _Sender>
requires __valid_sync_wait_argument<_Sender>
&& __has_implementation_for<sync_wait_t, __early_domain_of_t<_Sender>, _Sender>
auto operator()(_Sender&& __sndr) const -> std::optional<__value_tuple_for_t<_Sender>> {
using _Domain = __late_domain_of_t<_Sender, __env>;
return stdexec::apply_sender(_Domain(), *this, static_cast<_Sender&&>(__sndr));
template <class _Sender>
auto operator()(_Sender&& __sndr) const {
if constexpr (!sender_in<_Sender, __env>) {
stdexec::__diagnose_sender_concept_failure<_Sender, __env>();
} else {
using __domain_t = __late_domain_of_t<_Sender, __env>;
constexpr auto __success_completion_count =
__v<value_types_of_t<_Sender, __env, __types, __msize::__f>>;
static_assert(
__success_completion_count != 0,
"The argument to stdexec::sync_wait() is a sender that cannot complete successfully. "
"stdexec::sync_wait() requires a sender that can complete successfully in exactly one "
"way. In other words, the sender's completion signatures must include exactly one "
"signature of the form `set_value_t(value-types...)`.");
static_assert(
__success_completion_count <= 1,
"The sender passed to stdexec::sync_wait() can complete successfully in "
"more than one way. Use stdexec::sync_wait_with_variant() instead.");
if constexpr (1 == __success_completion_count) {
using __sync_wait_receiver = __receiver_t<_Sender>;
constexpr bool __no_custom_sync_wait = __same_as<__domain_t, default_domain>;
if constexpr (__no_custom_sync_wait && sender_to<_Sender, __sync_wait_receiver>) {
// using __connect_result = connect_result_t<_Sender, __sync_wait_receiver>;
// if constexpr (!operation_state<__connect_result>) {
// static_assert(
// operation_state<__connect_result>,
// "The `connect` member function of the sender passed to stdexec::sync_wait() does "
// "not return an operation state. An operation state is required to have a "
// "no-throw .start() member function.");
// } else
{
// success path, dispatch to the default domain's sync_wait
return default_domain().apply_sender(*this, static_cast<_Sender&&>(__sndr));
}
} else if constexpr (__no_custom_sync_wait) {
static_assert(
sender_to<_Sender, __sync_wait_receiver>,
"The sender passed to stdexec::sync_wait() does not have a .connect(<receiver>) "
"member function that accepts sync_wait's receiver.");
} else if constexpr (!__has_implementation_for<sync_wait_t, __domain_t, _Sender>) {
static_assert(
__has_implementation_for<sync_wait_t, __domain_t, _Sender>,
"The sender passed to stdexec::sync_wait() has a domain that does not provide a "
"usable implementation for sync_wait().");
} else {
// success path, dispatch to the custom domain's sync_wait
return stdexec::apply_sender(__domain_t(), *this, static_cast<_Sender&&>(__sndr));
}
}
}
}

#if STDEXEC_EDG()
// This is needed to get sensible diagnostics from nvc++
template <class _Sender, class _Error = __error_description_t<_Sender>>
auto operator()(_Sender&&, [[maybe_unused]] _Error __diagnostic = {}) const
-> std::optional<std::tuple<int>> = delete;
#endif

// clang-format off
/// @brief Synchronously wait for the result of a sender, blocking the
/// current thread.
Expand Down
2 changes: 1 addition & 1 deletion test/stdexec/algos/adaptors/test_then.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ namespace {
struct then_test_domain {
template <class Sender, class... Env>
requires std::same_as<ex::tag_of_t<Sender>, ex::then_t>
static auto transform_sender(Sender&& sndr, Env&&...) {
static auto transform_sender(Sender&&, Env&&...) {
return ex::just(std::string{"ciao"});
}
};
Expand Down
2 changes: 1 addition & 1 deletion test/stdexec/algos/consumers/test_sync_wait.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ namespace {
ex::sender auto snd = fallible_just{13} //
| ex::let_error(always(ex::just(std::string{"err"})));
check_val_types<ex::__mset<pack<int>, pack<std::string>>>(snd);
static_assert(!std::invocable<ex::sync_wait_t, decltype(snd)>);
// static_assert(!std::invocable<ex::sync_wait_t, decltype(snd)>);
}

TEST_CASE(
Expand Down
1 change: 1 addition & 0 deletions test/stdexec/queries/test_forwarding_queries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

STDEXEC_PRAGMA_IGNORE_GNU("-Wdeprecated-declarations")
STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity_with_custom_message)
STDEXEC_PRAGMA_IGNORE_MSVC(4996)

namespace ex = stdexec;

Expand Down
Loading