diff --git a/cmake/clangd_compile_info.cmake b/cmake/clangd_compile_info.cmake index d22813469..d30c540a2 100644 --- a/cmake/clangd_compile_info.cmake +++ b/cmake/clangd_compile_info.cmake @@ -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 diff --git a/include/stdexec/__detail/__basic_sender.hpp b/include/stdexec/__detail/__basic_sender.hpp index 36c0701b5..058a5d05f 100644 --- a/include/stdexec/__detail/__basic_sender.hpp +++ b/include/stdexec/__detail/__basic_sender.hpp @@ -140,7 +140,7 @@ namespace stdexec { }; static constexpr auto get_completion_signatures = // - [](_Sender&& __sndr, auto&&...) noexcept { + [](_Sender&&, auto&&...) noexcept { static_assert( __mnever>, "No customization of get_completion_signatures for this sender tag type."); diff --git a/include/stdexec/__detail/__completion_signatures.hpp b/include/stdexec/__detail/__completion_signatures.hpp index 743dcad41..5477933be 100644 --- a/include/stdexec/__detail/__completion_signatures.hpp +++ b/include/stdexec/__detail/__completion_signatures.hpp @@ -46,7 +46,7 @@ namespace stdexec { concept __valid_completion_signatures = // __same_as<__ok_t<_Completions>, __msuccess> && __sigs::__is_completion_signatures<_Completions>; - template + template 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 diff --git a/include/stdexec/__detail/__continues_on.hpp b/include/stdexec/__detail/__continues_on.hpp index 403674e4c..7f62ea07d 100644 --- a/include/stdexec/__detail/__continues_on.hpp +++ b/include/stdexec/__detail/__continues_on.hpp @@ -63,7 +63,7 @@ namespace stdexec { } template - 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()); } }; diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 36cb5565c..82b94e1a7 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -24,8 +24,8 @@ #include "__tag_invoke.hpp" #include "__tuple.hpp" -#include #include // IWYU pragma: keep for unwrap_reference_t +#include #include STDEXEC_PRAGMA_PUSH() @@ -211,7 +211,7 @@ namespace stdexec { struct get_domain_t { template requires tag_invocable - constexpr auto operator()(const _Ty& __ty) const noexcept + constexpr auto operator()(const _Ty&) const noexcept -> __decay_t> { static_assert( nothrow_tag_invocable, @@ -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 @@ -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 @@ -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 @@ -461,8 +455,6 @@ namespace stdexec { auto query(_Key) const noexcept -> const _Value& { return __value_; } - - auto operator=(const __with&) -> __with& = delete; }; template @@ -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; }; }; @@ -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; }; }; @@ -573,8 +561,6 @@ namespace stdexec { -> __call_result_t { return __fun_(_Tag()); } - - auto operator=(const __from&) -> __from& = delete; }; template diff --git a/include/stdexec/__detail/__senders.hpp b/include/stdexec/__detail/__senders.hpp index 9a290e495..316ac886d 100644 --- a/include/stdexec/__detail/__senders.hpp +++ b/include/stdexec/__detail/__senders.hpp @@ -30,6 +30,7 @@ #include "__transform_completion_signatures.hpp" #include "__transform_sender.hpp" #include "__type_traits.hpp" +#include namespace stdexec { ///////////////////////////////////////////////////////////////////////////// @@ -335,4 +336,54 @@ namespace stdexec { template 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 concept check fails. + template + 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 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 concept check has failed. This is likely a " + "bug in the sender implementation."); + } + } + } } // namespace stdexec diff --git a/include/stdexec/__detail/__sync_wait.hpp b/include/stdexec/__detail/__sync_wait.hpp index 400042587..d1608841e 100644 --- a/include/stdexec/__detail/__sync_wait.hpp +++ b/include/stdexec/__detail/__sync_wait.hpp @@ -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 - 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 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 - using __error_description_t = decltype(__sync_wait::__diagnose_error<_Sender>()); -#endif - //////////////////////////////////////////////////////////////////////////// // [execution.senders.consumers.sync_wait] struct sync_wait_t { - template _Sender> - requires __valid_sync_wait_argument<_Sender> - && __has_implementation_for, _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 + 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>; + 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() " + "member function that accepts sync_wait's receiver."); + } else if constexpr (!__has_implementation_for) { + static_assert( + __has_implementation_for, + "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 > - auto operator()(_Sender&&, [[maybe_unused]] _Error __diagnostic = {}) const - -> std::optional> = delete; -#endif - // clang-format off /// @brief Synchronously wait for the result of a sender, blocking the /// current thread. diff --git a/test/stdexec/algos/adaptors/test_then.cpp b/test/stdexec/algos/adaptors/test_then.cpp index d89b39230..7031e17f7 100644 --- a/test/stdexec/algos/adaptors/test_then.cpp +++ b/test/stdexec/algos/adaptors/test_then.cpp @@ -184,7 +184,7 @@ namespace { struct then_test_domain { template requires std::same_as, ex::then_t> - static auto transform_sender(Sender&& sndr, Env&&...) { + static auto transform_sender(Sender&&, Env&&...) { return ex::just(std::string{"ciao"}); } }; diff --git a/test/stdexec/algos/consumers/test_sync_wait.cpp b/test/stdexec/algos/consumers/test_sync_wait.cpp index 12422835d..696da94d6 100644 --- a/test/stdexec/algos/consumers/test_sync_wait.cpp +++ b/test/stdexec/algos/consumers/test_sync_wait.cpp @@ -117,7 +117,7 @@ namespace { ex::sender auto snd = fallible_just{13} // | ex::let_error(always(ex::just(std::string{"err"}))); check_val_types, pack>>(snd); - static_assert(!std::invocable); + // static_assert(!std::invocable); } TEST_CASE( diff --git a/test/stdexec/queries/test_forwarding_queries.cpp b/test/stdexec/queries/test_forwarding_queries.cpp index cda9f149d..ec558717e 100644 --- a/test/stdexec/queries/test_forwarding_queries.cpp +++ b/test/stdexec/queries/test_forwarding_queries.cpp @@ -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;