From 3ede965c45674a78ab5f1311defc4cc3e55c826a Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 2 Aug 2025 14:16:43 -0700 Subject: [PATCH 1/7] add the ability to co_await sender adaptor closure objects --- include/exec/task.hpp | 32 ++++++++-- include/stdexec/__detail/__config.hpp | 20 +------ include/stdexec/__detail/__env.hpp | 61 +++++++++++++++++--- include/stdexec/__detail/__execution_fwd.hpp | 4 ++ include/stdexec/__detail/__just.hpp | 5 +- include/stdexec/__detail/__let.hpp | 4 +- include/stdexec/__detail/__read_env.hpp | 5 +- include/stdexec/__detail/__tag_invoke.hpp | 4 +- include/stdexec/__detail/__then.hpp | 2 +- test/exec/test_task.cpp | 38 ++++++++++++ test/stdexec/algos/adaptors/test_then.cpp | 3 + 11 files changed, 139 insertions(+), 39 deletions(-) diff --git a/include/exec/task.hpp b/include/exec/task.hpp index 603c7edb2..e84a0d511 100644 --- a/include/exec/task.hpp +++ b/include/exec/task.hpp @@ -88,8 +88,6 @@ namespace exec { __sticky }; - struct __parent_promise_t { }; - template <__scheduler_affinity _SchedulerAffinity = __scheduler_affinity::__sticky> class __default_task_context_impl { template @@ -103,7 +101,7 @@ namespace exec { public: template - explicit __default_task_context_impl(__parent_promise_t, _ParentPromise& __parent) noexcept { + explicit __default_task_context_impl(_ParentPromise& __parent) noexcept { if constexpr (_SchedulerAffinity == __scheduler_affinity::__sticky) { if constexpr (__check_parent_promise_has_scheduler<_ParentPromise>()) { __scheduler_ = get_scheduler(get_env(__parent)); @@ -281,6 +279,25 @@ namespace exec { __variant_for<__void, std::exception_ptr> __data_{}; }; + template + struct __just_void { + using sender_concept = sender_t; + using completion_signatures = completion_signatures; + + template + [[nodiscard]] + static constexpr auto connect(_Rcvr __rcvr) noexcept { + return stdexec::connect(just(), static_cast<_Rcvr&&>(__rcvr)); + } + + [[nodiscard]] + constexpr auto get_env() const noexcept { + return prop{get_completion_scheduler, __sch_}; + } + + _Sch __sch_; + }; + enum class disposition : unsigned { stopped, succeeded, @@ -319,6 +336,8 @@ namespace exec { } private: + using __scheduler_t = __query_result_or_t; + struct __final_awaitable { static constexpr auto await_ready() noexcept -> bool { return false; @@ -399,6 +418,11 @@ namespace exec { } #endif + template <__sender_adaptor_closure_for<__just_void<__scheduler_t>> _Closure> + auto await_transform(_Closure&& __closure) noexcept -> decltype(auto) { + return await_transform(static_cast<_Closure&&>(__closure)(__just_void<__scheduler_t>())); + } + template auto await_transform(_Awaitable&& __awaitable) noexcept -> decltype(auto) { return with_awaitable_senders<__promise>::await_transform( @@ -431,7 +455,7 @@ namespace exec { auto await_suspend(__coro::coroutine_handle<_ParentPromise2> __parent) noexcept -> __coro::coroutine_handle<> { static_assert(__one_of<_ParentPromise, _ParentPromise2, void>); - __coro_.promise().__context_.emplace(__parent_promise_t(), __parent.promise()); + __coro_.promise().__context_.emplace(__parent.promise()); __context_.emplace(*__coro_.promise().__context_, __parent.promise()); __coro_.promise().set_continuation(__parent); if constexpr (requires { __coro_.promise().stop_requested() ? 0 : 1; }) { diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 3c4e6fb68..f36e5ea77 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -473,25 +473,7 @@ namespace stdexec { # define STDEXEC_EXPLICIT_THIS(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif -// Configure extra type checking -#define STDEXEC_TYPE_CHECKING_ZERO() 0 -#define STDEXEC_TYPE_CHECKING_ONE() 1 -#define STDEXEC_TYPE_CHECKING_TWO() 2 - -#define STDEXEC_PROBE_TYPE_CHECKING_ STDEXEC_TYPE_CHECKING_ONE -#define STDEXEC_PROBE_TYPE_CHECKING_0 STDEXEC_TYPE_CHECKING_ZERO -#define STDEXEC_PROBE_TYPE_CHECKING_1 STDEXEC_TYPE_CHECKING_ONE -#define STDEXEC_PROBE_TYPE_CHECKING_STDEXEC_ENABLE_EXTRA_TYPE_CHECKING STDEXEC_TYPE_CHECKING_TWO - -#define STDEXEC_TYPE_CHECKING_WHICH3(...) STDEXEC_PROBE_TYPE_CHECKING_##__VA_ARGS__ -#define STDEXEC_TYPE_CHECKING_WHICH2(...) STDEXEC_TYPE_CHECKING_WHICH3(__VA_ARGS__) -#define STDEXEC_TYPE_CHECKING_WHICH STDEXEC_TYPE_CHECKING_WHICH2(STDEXEC_ENABLE_EXTRA_TYPE_CHECKING) - -#ifndef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING -# define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 0 -#elif STDEXEC_TYPE_CHECKING_WHICH() == 2 -// do nothing -#elif STDEXEC_TYPE_CHECKING_WHICH() == 0 +#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING == 0 # undef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING # define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 0 #else diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 72a2bd331..c782e3adf 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -246,13 +246,13 @@ namespace stdexec { struct __is_scheduler_affine_t { template - constexpr auto operator()(const _Env&) const noexcept { - if constexpr (tag_invocable<__is_scheduler_affine_t, const _Env&>) { - using _Result = __decay_t>; - static_assert(__same_as), const bool>); - return _Result(); + constexpr auto operator()(const _Env& __env) const noexcept { + if constexpr (requires { _Env::query(*this); }) { + return _Env::query(*this); + } else if constexpr (requires { __env.query(*this); }) { + return __env.query(*this); } else { - return std::false_type(); + return false; } } @@ -410,6 +410,18 @@ namespace stdexec { STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE prop(_Query, _Value) -> prop<_Query, std::unwrap_reference_t<_Value>>; + template + struct cprop { + using __t = cprop; + using __id = cprop; + + STDEXEC_ATTRIBUTE(nodiscard) + + static constexpr auto query(_Query) noexcept { + return _Value; + } + }; + // utility for joining multiple environments template struct env { @@ -432,6 +444,23 @@ namespace stdexec { template using __1st_env_t = decltype(env::__get_1st<_Query, _Args...>(__declval())); + // NOT TO SPEC: a static query memfn for those envs that have a static query memfn. + // This is useful for constexpr evaluation of queries. + template + requires(__queryable<_Envs, _Query, _Args...> || ...) + STDEXEC_ATTRIBUTE(always_inline) + static constexpr auto query(_Query __q, _Args&&... __args) + noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) + -> decltype(auto) + requires requires { + std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( + __q, static_cast<_Args &&>(__args)...); + } + { + return std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( + __q, static_cast<_Args&&>(__args)...); + } + template requires(__queryable<_Envs, _Query, _Args...> || ...) STDEXEC_ATTRIBUTE(always_inline) @@ -467,6 +496,24 @@ namespace stdexec { template using __1st_env_t = decltype(env::__get_1st<_Query, _Args...>(__declval())); + // NOT TO SPEC: a static query memfn for those envs that have a static query memfn. + // This is useful for constexpr evaluation of queries. + template + requires __queryable<_Env0, _Query, _Args...> + || __queryable<_Env1, _Query, _Args...> + STDEXEC_ATTRIBUTE(always_inline) + static constexpr auto query(_Query __q, _Args&&... __args) + noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) + -> decltype(auto) + requires requires { + std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( + __q, static_cast<_Args &&>(__args)...); + } + { + return std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( + __q, static_cast<_Args&&>(__args)...); + } + template requires __queryable<_Env0, _Query, _Args...> || __queryable<_Env1, _Query, _Args...> STDEXEC_ATTRIBUTE(always_inline) @@ -765,7 +812,7 @@ namespace stdexec { template concept __is_scheduler_affine = requires { - requires __v<__call_result_t<__is_scheduler_affine_t, env_of_t<_Sender>>>; + requires env_of_t<_Sender>::query(__is_scheduler_affine_t{}); }; } // namespace stdexec diff --git a/include/stdexec/__detail/__execution_fwd.hpp b/include/stdexec/__detail/__execution_fwd.hpp index a31e5a767..ddbc9390c 100644 --- a/include/stdexec/__detail/__execution_fwd.hpp +++ b/include/stdexec/__detail/__execution_fwd.hpp @@ -54,11 +54,15 @@ namespace stdexec { template struct prop; + template + struct cprop; + template struct env; } // namespace __env using __env::prop; + using __env::cprop; using __env::env; using empty_env [[deprecated("stdexec::empty_env is now spelled stdexec::env<>")]] = env<>; diff --git a/include/stdexec/__detail/__just.hpp b/include/stdexec/__detail/__just.hpp index 7bc705170..1edff558c 100644 --- a/include/stdexec/__detail/__just.hpp +++ b/include/stdexec/__detail/__just.hpp @@ -34,8 +34,9 @@ namespace stdexec { struct __impl : __sexpr_defaults { using __tag_t = typename _JustTag::__tag_t; - static constexpr auto get_attrs = [](__ignore) noexcept { - return prop{__is_scheduler_affine_t{}, std::true_type{}}; + static constexpr auto get_attrs = + [](__ignore) noexcept -> cprop<__is_scheduler_affine_t, true> { + return {}; }; static constexpr auto get_completion_signatures = diff --git a/include/stdexec/__detail/__let.hpp b/include/stdexec/__detail/__let.hpp index 0617c805a..6c4d2a4d9 100644 --- a/include/stdexec/__detail/__let.hpp +++ b/include/stdexec/__detail/__let.hpp @@ -41,7 +41,7 @@ namespace stdexec { // A dummy scheduler that is used by the metaprogramming below when the input sender doesn't // have a completion scheduler. struct __unknown_scheduler { - struct __env { + struct __attrs { static constexpr auto query(__is_scheduler_affine_t) noexcept -> bool { return true; } @@ -56,7 +56,7 @@ namespace stdexec { using sender_concept = sender_t; [[nodiscard]] - constexpr auto get_env() const noexcept -> __env { + constexpr auto get_env() const noexcept -> __attrs { return {}; } }; diff --git a/include/stdexec/__detail/__read_env.hpp b/include/stdexec/__detail/__read_env.hpp index 25f02a55f..bc1acad95 100644 --- a/include/stdexec/__detail/__read_env.hpp +++ b/include/stdexec/__detail/__read_env.hpp @@ -82,8 +82,9 @@ namespace stdexec { using __completions_t = __minvoke<__mtry_catch_q<__read::__completions_t, __q<__query_failed_error>>, _Tag, _Env>; - static constexpr auto get_attrs = [](__ignore) noexcept { - return prop{__is_scheduler_affine_t{}, std::true_type{}}; + static constexpr auto get_attrs = + [](__ignore) noexcept -> cprop<__is_scheduler_affine_t, true> { + return {}; }; static constexpr auto get_completion_signatures = diff --git a/include/stdexec/__detail/__tag_invoke.hpp b/include/stdexec/__detail/__tag_invoke.hpp index 11b878dc5..0f2b908e3 100644 --- a/include/stdexec/__detail/__tag_invoke.hpp +++ b/include/stdexec/__detail/__tag_invoke.hpp @@ -42,8 +42,8 @@ namespace stdexec { template requires true // so this overload is preferred over the one below STDEXEC_ATTRIBUTE(always_inline) constexpr auto tag_invoke(_Tag, const _Env&) noexcept - -> __mconstant<_Env::query(_Tag())> { - return {}; + -> decltype(_Env::query(_Tag())) { + return _Env::query(_Tag()); } // For handling queryables with a query member function: diff --git a/include/stdexec/__detail/__then.hpp b/include/stdexec/__detail/__then.hpp index 09e541293..a035a110d 100644 --- a/include/stdexec/__detail/__then.hpp +++ b/include/stdexec/__detail/__then.hpp @@ -62,7 +62,7 @@ namespace stdexec { struct __then_impl : __sexpr_defaults { static constexpr auto get_attrs = [](__ignore, const _Child& __child) noexcept { return __env::__join( - prop{__is_scheduler_affine_t{}, __mbool<__is_scheduler_affine<_Child>>{}}, + cprop<__is_scheduler_affine_t, __is_scheduler_affine<_Child>>{}, stdexec::get_env(__child)); }; diff --git a/test/exec/test_task.cpp b/test/exec/test_task.cpp index 23e9c4961..405d0b5d0 100644 --- a/test/exec/test_task.cpp +++ b/test/exec/test_task.cpp @@ -22,10 +22,15 @@ # include # include +# include + # include +# include + using namespace exec; using namespace stdexec; +using namespace std::string_literals; namespace { @@ -252,6 +257,39 @@ namespace { CHECK(count == 3); } + struct test_domain { + template _Sender> + static constexpr auto transform_sender(_Sender&& __sndr) noexcept { + return just("goodbye"s); + } + }; + + struct test_task_context { + constexpr test_task_context(auto&&...) noexcept { + } + + template + using promise_context_t = test_task_context; + + template + using awaiter_context_t = test_task_context; + + static constexpr auto query(get_scheduler_t) noexcept { + return basic_inline_scheduler{}; + } + }; + + template + using test_task = exec::basic_task; + + TEST_CASE("task - can co_await a sender adaptor closure object", "[types][task]") { + auto salutation = []() -> test_task { + co_return co_await then([] { return "hello"s; }); + }(); + auto [msg] = stdexec::sync_wait(std::move(salutation)).value(); + CHECK(msg == "goodbye"s); + } + # if !STDEXEC_STD_NO_EXCEPTIONS() TEST_CASE("task - can error early", "[types][task]") { int count = 0; diff --git a/test/stdexec/algos/adaptors/test_then.cpp b/test/stdexec/algos/adaptors/test_then.cpp index 88294ccb0..d1d1c5ad0 100644 --- a/test/stdexec/algos/adaptors/test_then.cpp +++ b/test/stdexec/algos/adaptors/test_then.cpp @@ -23,6 +23,9 @@ namespace ex = stdexec; +// Check that the `then` algorithm is correctly forwarding the __is_scheduler_affine query +static_assert(ex::__is_scheduler_affine); + namespace { TEST_CASE("then returns a sender", "[adaptors][then]") { auto snd = ex::then(ex::just(), [] { }); From 4c9615622a5d9649c453daec04326f287cc06bc8 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 3 Aug 2025 18:47:31 -0700 Subject: [PATCH 2/7] portability fixes --- include/exec/__detail/__bit_cast.hpp | 2 +- include/exec/__detail/__numa.hpp | 13 ++--- include/exec/task.hpp | 2 +- include/exec/timed_scheduler.hpp | 4 +- include/stdexec/__detail/__config.hpp | 53 +++++++++++++------ .../stdexec/__detail/__connect_awaitable.hpp | 2 +- include/stdexec/__detail/__cpo.hpp | 4 +- include/stdexec/__detail/__env.hpp | 25 ++++++--- include/stdexec/__detail/__receivers.hpp | 2 +- include/stdexec/__detail/__tuple.hpp | 3 +- include/stdexec/coroutine.hpp | 2 +- test/exec/async_scope/test_empty.cpp | 4 +- test/exec/test_timed_thread_scheduler.cpp | 5 +- 13 files changed, 73 insertions(+), 48 deletions(-) diff --git a/include/exec/__detail/__bit_cast.hpp b/include/exec/__detail/__bit_cast.hpp index 77b726fd5..533aa1451 100644 --- a/include/exec/__detail/__bit_cast.hpp +++ b/include/exec/__detail/__bit_cast.hpp @@ -39,7 +39,7 @@ namespace exec { requires(sizeof(_To) == sizeof(_From)) [[nodiscard]] constexpr _To bit_cast(const _From& __from) noexcept { -# if STDEXEC_HAS_BUILTIN(__builtin_bit_cast) || (_MSC_VER >= 1926) +# if STDEXEC_HAS_BUILTIN(__builtin_bit_cast) || (STDEXEC_MSVC() && STDEXEC_MSVC_VERSION >= 19'26) return __builtin_bit_cast(_To, __from); # else _To __to; diff --git a/include/exec/__detail/__numa.hpp b/include/exec/__detail/__numa.hpp index b373f6a28..f8303d453 100644 --- a/include/exec/__detail/__numa.hpp +++ b/include/exec/__detail/__numa.hpp @@ -17,7 +17,6 @@ #pragma once #include "../../stdexec/__detail/__config.hpp" -#include "../../stdexec/__detail/__meta.hpp" #include "../scope.hpp" // IWYU pragma: keep #include // IWYU pragma: keep @@ -27,14 +26,10 @@ #include #include -// Work around a bug in the NVHPC compilers prior to version 24.03 -#if STDEXEC_NVHPC() -# if STDEXEC_NVHPC_VERSION() <= 2403 -# define STDEXEC_NUMA_VTABLE_INLINE -# endif -#endif - -#ifndef STDEXEC_NUMA_VTABLE_INLINE +// Work around a bug in the NVHPC compilers prior to version 24.3 +#if STDEXEC_NVHPC() && STDEXEC_NVHPC_VERSION < 24'03 +# define STDEXEC_NUMA_VTABLE_INLINE +#else # define STDEXEC_NUMA_VTABLE_INLINE inline #endif diff --git a/include/exec/task.hpp b/include/exec/task.hpp index e84a0d511..f97cd1c8e 100644 --- a/include/exec/task.hpp +++ b/include/exec/task.hpp @@ -282,7 +282,7 @@ namespace exec { template struct __just_void { using sender_concept = sender_t; - using completion_signatures = completion_signatures; + using completion_signatures = stdexec::completion_signatures; template [[nodiscard]] diff --git a/include/exec/timed_scheduler.hpp b/include/exec/timed_scheduler.hpp index 193cf8216..c2c425a8d 100644 --- a/include/exec/timed_scheduler.hpp +++ b/include/exec/timed_scheduler.hpp @@ -126,7 +126,7 @@ namespace exec { }; struct schedule_after_t : __schedule_after_base_t { -#if !STDEXEC_CLANG() || (__clang_major__ >= 16) +#if !STDEXEC_CLANG() || STDEXEC_CLANG_VERSION >= 16'00 using __schedule_after_base_t::operator(); #else // clang prior to 16 is not able to find the correct overload in the @@ -198,7 +198,7 @@ namespace exec { }; struct schedule_at_t : __schedule_at_base_t { -#if !STDEXEC_CLANG() || (__clang_major__ >= 16) +#if !STDEXEC_CLANG() || STDEXEC_CLANG_VERSION >= 16'00 using __schedule_at_base_t::operator(); #else // clang prior to 16 is not able to find the correct overload in the diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index f36e5ea77..7b800afda 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -48,10 +48,13 @@ // macro name; nothing, otherwise. #if defined(__NVCC__) # define STDEXEC_NVCC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +# define STDEXEC_NVCC_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) #elif defined(__EDG__) # define STDEXEC_EDG(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +# define STDEXEC_EDG_VERSION __EDG_VERSION__ # if defined(__NVCOMPILER) # define STDEXEC_NVHPC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +# define STDEXEC_NVHPC_VERSION (__NVCOMPILER_MAJOR__ * 100 + __NVCOMPILER_MINOR__) # endif # if defined(__INTELLISENSE__) # define STDEXEC_INTELLISENSE(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) @@ -59,17 +62,23 @@ # endif #elif defined(__clang__) # define STDEXEC_CLANG(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +# define STDEXEC_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) # if defined(_MSC_VER) # define STDEXEC_CLANG_CL(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) # endif # if defined(__apple_build_version__) # define STDEXEC_APPLE_CLANG(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +// Apple clang version is encoded as major * 1000000 + minor * 1000 + patch. We ignore the patch +// version here, as it is not relevant for the purposes of this library. +# define STDEXEC_APPLE_CLANG_VERSION (__apple_build_version__ / 1000) # endif #elif defined(__GNUC__) # define STDEXEC_GCC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +# define STDEXEC_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) #elif defined(_MSC_VER) # define STDEXEC_MSVC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) # define STDEXEC_MSVC_HEADERS(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +# define STDEXEC_MSVC_VERSION _MSC_VER #endif #ifndef STDEXEC_NVCC @@ -103,10 +112,6 @@ # define STDEXEC_INTELLISENSE(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif -#if STDEXEC_NVHPC() -# define STDEXEC_NVHPC_VERSION() (__NVCOMPILER_MAJOR__ * 100 + __NVCOMPILER_MINOR__) -#endif - //////////////////////////////////////////////////////////////////////////////////////////////////// #if defined(__CUDACC__) || STDEXEC_NVHPC() # define STDEXEC_CUDA_COMPILATION(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) @@ -173,13 +178,25 @@ namespace __coro = std::experimental; #if STDEXEC_NVHPC() // NVBUG #4067067: NVHPC does not fully support [[no_unique_address]] -# define STDEXEC_ATTR_WHICH_3(_ATTR) /*nothing*/ +# if STDEXEC_NVHPC_VERSION < 23'05 +# define STDEXEC_ATTR_WHICH_3(_ATTR) /*nothing*/ +# else +# define STDEXEC_ATTR_WHICH_3(_ATTR) [[no_unique_address]] +# endif +#elif STDEXEC_CLANG_CL() +// clang-cl does not support [[no_unique_address]]: https://reviews.llvm.org/D110485 +# if STDEXEC_CLANG_VERSION < 18'01 // TODO: Find the version that started supporting [[msvc::no_unique_address]] +# define STDEXEC_ATTR_WHICH_3(_ATTR) /*nothing*/ +# else +# define STDEXEC_ATTR_WHICH_3(_ATTR) [[msvc::no_unique_address]] +# endif #elif STDEXEC_MSVC() // MSVCBUG https://developercommunity.visualstudio.com/t/Incorrect-codegen-when-using-msvc::no_/10452874 -# define STDEXEC_ATTR_WHICH_3(_ATTR) // [[msvc::no_unique_address]] -#elif STDEXEC_CLANG_CL() -// clang-cl does not support: https://reviews.llvm.org/D110485 -# define STDEXEC_ATTR_WHICH_3(_ATTR) // [[msvc::no_unique_address]] +# if STDEXEC_MSVC_VERSION < 19'43 +# define STDEXEC_ATTR_WHICH_3(_ATTR) /*nothing*/ +# else +# define STDEXEC_ATTR_WHICH_3(_ATTR) [[msvc::no_unique_address]] +# endif #else # define STDEXEC_ATTR_WHICH_3(_ATTR) [[no_unique_address]] #endif @@ -190,7 +207,7 @@ namespace __coro = std::experimental; #elif STDEXEC_CLANG() # define STDEXEC_ATTR_WHICH_4(_ATTR) \ __attribute__((__always_inline__, __artificial__, __nodebug__)) inline -#elif defined(__GNUC__) +#elif STDEXEC_GCC() # define STDEXEC_ATTR_WHICH_4(_ATTR) __attribute__((__always_inline__, __artificial__)) inline #else # define STDEXEC_ATTR_WHICH_4(_ATTR) /*nothing*/ @@ -272,7 +289,7 @@ namespace __coro = std::experimental; # define STDEXEC_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable_v<__VA_ARGS__> #endif -#if STDEXEC_HAS_BUILTIN(__is_base_of) || (_MSC_VER >= 1914) +#if STDEXEC_HAS_BUILTIN(__is_base_of) || (STDEXEC_MSVC_VERSION >= 19'14) # define STDEXEC_IS_BASE_OF(...) __is_base_of(__VA_ARGS__) #else # define STDEXEC_IS_BASE_OF(...) std::is_base_of_v<__VA_ARGS__> @@ -371,17 +388,19 @@ namespace stdexec { #endif // Before gcc-12, gcc really didn't like tuples or variants of immovable types -#if STDEXEC_GCC() && (__GNUC__ < 12) +#if STDEXEC_GCC() && (STDEXEC_GCC_VERSION < 12'00) # define STDEXEC_IMMOVABLE(_XP) _XP(_XP&&) #else # define STDEXEC_IMMOVABLE(_XP) _XP(_XP&&) = delete #endif +#if STDEXEC_GCC() // BUG (gcc#98995): copy elision fails when initializing a [[no_unique_address]] field // from a function returning an object of class type by value. -// // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98995 -#if STDEXEC_GCC() +# define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS +#elif STDEXEC_CLANG() && (__clang_major__ >= 15 && __clang_major__ < 19) +// See https://github.com/llvm/llvm-project/issues/93563 # define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS #else # define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS STDEXEC_ATTRIBUTE(no_unique_address) @@ -401,7 +420,7 @@ namespace stdexec { // Some compilers turn on pack indexing in pre-C++26 code. We want to use it if it is // available. Pack indexing is disabled for clang < 20 because of: // https://github.com/llvm/llvm-project/issues/116105 -#if defined(__cpp_pack_indexing) && !STDEXEC_NVCC() && !(STDEXEC_CLANG() && __clang_major__ < 20) +#if defined(__cpp_pack_indexing) && !STDEXEC_NVCC() && !(STDEXEC_CLANG() && STDEXEC_CLANG_VERSION < 20'00) # define STDEXEC_HAS_PACK_INDEXING() 1 #else // ^^^ has pack indexing ^^^ / vvv no pack indexing vvv # define STDEXEC_HAS_PACK_INDEXING() 0 @@ -416,7 +435,7 @@ namespace stdexec { // Before clang-16, clang did not like libstdc++'s ranges implementation #if __has_include() && \ (defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L) && \ - (!STDEXEC_CLANG() || __clang_major__ >= 16 || defined(_LIBCPP_VERSION)) + (!STDEXEC_CLANG() || STDEXEC_CLANG_VERSION >= 16'00 || defined(_LIBCPP_VERSION)) # define STDEXEC_HAS_STD_RANGES() 1 #else # define STDEXEC_HAS_STD_RANGES() 0 @@ -461,7 +480,7 @@ namespace stdexec { } // GCC 13 implements lexical friendship, but it is incomplete. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111018 -#if STDEXEC_CLANG() // || (STDEXEC_GCC() && __GNUC__ >= 13) +#if STDEXEC_CLANG() // || (STDEXEC_GCC() && STDEXEC_GCC_VERSION >= 13'00) # define STDEXEC_FRIENDSHIP_IS_LEXICAL() 1 #else # define STDEXEC_FRIENDSHIP_IS_LEXICAL() 0 diff --git a/include/stdexec/__detail/__connect_awaitable.hpp b/include/stdexec/__detail/__connect_awaitable.hpp index ebf2d7e39..7191050c6 100644 --- a/include/stdexec/__detail/__connect_awaitable.hpp +++ b/include/stdexec/__detail/__connect_awaitable.hpp @@ -173,7 +173,7 @@ namespace stdexec { } template -# if STDEXEC_GCC() && (__GNUC__ > 11) +# if STDEXEC_GCC() && (STDEXEC_GCC_VERSION >= 12'00) __attribute__((__used__)) # endif static auto __co_impl(_Awaitable __awaitable, _Receiver __rcvr) -> __operation_t<_Receiver> { diff --git a/include/stdexec/__detail/__cpo.hpp b/include/stdexec/__detail/__cpo.hpp index 991aa26a5..e9895fe60 100644 --- a/include/stdexec/__detail/__cpo.hpp +++ b/include/stdexec/__detail/__cpo.hpp @@ -82,7 +82,7 @@ # pragma deprecated(STDEXEC_CUSTOM) #endif -#if STDEXEC_GCC() || (STDEXEC_CLANG() && __clang_major__ < 14) +#if STDEXEC_GCC() || (STDEXEC_CLANG() && STDEXEC_CLANG_VERSION < 14'00) # define STDEXEC_CUSTOM \ _Pragma("GCC warning \"STDEXEC_CUSTOM is deprecated; use STDEXEC_MEMFN_DECL instead.\"") \ STDEXEC_MEMFN_DECL @@ -90,7 +90,7 @@ # define STDEXEC_CUSTOM STDEXEC_MEMFN_DECL #endif -#if STDEXEC_CLANG() && __clang_major__ >= 14 +#if STDEXEC_CLANG() && STDEXEC_CLANG_VERSION >= 14'00 # pragma clang deprecated(STDEXEC_CUSTOM, "use STDEXEC_MEMFN_DECL instead.") #endif diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index c782e3adf..ca8253678 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -423,12 +423,21 @@ namespace stdexec { }; // utility for joining multiple environments + template + struct env; + + template <> + struct env<> { + using __t = env; + using __id = env; + }; + template struct env { using __t = env; using __id = env; - __tuple_for<_Envs...> __tup_; + __tuple_for<_Envs..., env<>> __tup_; // return a reference to the first child env for which // __queryable<_Envs, _Query, _Args...> is true. @@ -436,7 +445,7 @@ namespace stdexec { STDEXEC_ATTRIBUTE(always_inline) static constexpr auto __get_1st(const env& __self) noexcept -> decltype(auto) { // NOLINTNEXTLINE (modernize-avoid-c-arrays) - constexpr bool __flags[] = {__queryable<_Envs, _Query, _Args...>...}; + constexpr bool __flags[] = {__queryable<_Envs, _Query, _Args...>..., true}; constexpr std::size_t __idx = __pos_of(__flags, __flags + sizeof...(_Envs)); return __self.__tup_.template __get<__idx>(__self.__tup_); } @@ -448,7 +457,7 @@ namespace stdexec { // This is useful for constexpr evaluation of queries. template requires(__queryable<_Envs, _Query, _Args...> || ...) - STDEXEC_ATTRIBUTE(always_inline) + STDEXEC_ATTRIBUTE(nodiscard, always_inline) static constexpr auto query(_Query __q, _Args&&... __args) noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) -> decltype(auto) @@ -463,7 +472,7 @@ namespace stdexec { template requires(__queryable<_Envs, _Query, _Args...> || ...) - STDEXEC_ATTRIBUTE(always_inline) + STDEXEC_ATTRIBUTE(nodiscard, always_inline) constexpr auto query(_Query __q, _Args&&... __args) const noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) -> decltype(auto) { @@ -501,7 +510,7 @@ namespace stdexec { template requires __queryable<_Env0, _Query, _Args...> || __queryable<_Env1, _Query, _Args...> - STDEXEC_ATTRIBUTE(always_inline) + STDEXEC_ATTRIBUTE(nodiscard, always_inline) static constexpr auto query(_Query __q, _Args&&... __args) noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) -> decltype(auto) @@ -516,7 +525,7 @@ namespace stdexec { template requires __queryable<_Env0, _Query, _Args...> || __queryable<_Env1, _Query, _Args...> - STDEXEC_ATTRIBUTE(always_inline) + STDEXEC_ATTRIBUTE(nodiscard, always_inline) constexpr auto query(_Query __q, _Args&&... __args) const noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) -> decltype(auto) { @@ -568,7 +577,7 @@ namespace stdexec { using __id = __fwd; STDEXEC_ATTRIBUTE(no_unique_address) _Env __env_; -#if STDEXEC_GCC() && __GNUC__ < 12 +#if STDEXEC_GCC() && STDEXEC_GCC_VERSION < 12'00 using __cvref_env_t = std::add_const_t<_Env>&; #else using __cvref_env_t = const _Env&; @@ -613,7 +622,7 @@ namespace stdexec { using __id = __without_; _Env __env_; -#if STDEXEC_GCC() && __GNUC__ < 12 +#if STDEXEC_GCC() && STDEXEC_GCC_VERSION < 12'00 using __cvref_env_t = std::add_const_t<_Env>&; #else using __cvref_env_t = const _Env&; diff --git a/include/stdexec/__detail/__receivers.hpp b/include/stdexec/__detail/__receivers.hpp index 44d9e3da5..ceb9bfda4 100644 --- a/include/stdexec/__detail/__receivers.hpp +++ b/include/stdexec/__detail/__receivers.hpp @@ -148,7 +148,7 @@ namespace stdexec { namespace __detail { template concept __enable_receiver = - (STDEXEC_EDG(requires { typename _Receiver::receiver_concept; } &&) + (STDEXEC_WHEN(STDEXEC_EDG(), requires { typename _Receiver::receiver_concept; } &&) derived_from) || requires { typename _Receiver::is_receiver; } // back-compat, NOT TO SPEC || STDEXEC_IS_BASE_OF(receiver_t, _Receiver); // NOT TO SPEC, for receiver_adaptor diff --git a/include/stdexec/__detail/__tuple.hpp b/include/stdexec/__detail/__tuple.hpp index 67efde0f6..ece94ffb5 100644 --- a/include/stdexec/__detail/__tuple.hpp +++ b/include/stdexec/__detail/__tuple.hpp @@ -39,8 +39,7 @@ namespace stdexec { namespace __tup { template struct __box { - // See https://github.com/llvm/llvm-project/issues/93563 - //STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS + STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS _Ty __value; }; diff --git a/include/stdexec/coroutine.hpp b/include/stdexec/coroutine.hpp index 4b7a80de3..8279b6996 100644 --- a/include/stdexec/coroutine.hpp +++ b/include/stdexec/coroutine.hpp @@ -18,7 +18,7 @@ #include "__detail/__awaitable.hpp" // IWYU pragma: export #include "__detail/__config.hpp" -#if STDEXEC_MSVC() && _MSC_VER <= 1939 +#if STDEXEC_MSVC() && STDEXEC_MSVC_VERSION <= 19'39 namespace stdexec { // MSVCBUG https://developercommunity.visualstudio.com/t/destroy-coroutine-from-final_suspend-r/10096047 diff --git a/test/exec/async_scope/test_empty.cpp b/test/exec/async_scope/test_empty.cpp index dac33a1d2..0fbda6818 100644 --- a/test/exec/async_scope/test_empty.cpp +++ b/test/exec/async_scope/test_empty.cpp @@ -90,7 +90,7 @@ namespace { } // TODO: GCC-11 generates warnings (treated as errors) for the following test -#if defined(__clang__) || !defined(__GNUC__) +#if !STDEXEC_GCC() || (STDEXEC_GCC_VERSION >= 12'00) TEST_CASE("waiting on work that spawns more work", "[async_scope][empty]") { impulse_scheduler sch; async_scope scope; @@ -138,7 +138,7 @@ namespace { #endif // TODO: GCC-11 generates warnings (treated as errors) for the following test -#if defined(__clang__) || !defined(__GNUC__) +#if !STDEXEC_GCC() || (STDEXEC_GCC_VERSION >= 12'00) TEST_CASE( "async_scope is empty after adding work when in cancelled state", "[async_scope][empty]") { diff --git a/test/exec/test_timed_thread_scheduler.cpp b/test/exec/test_timed_thread_scheduler.cpp index 060f81232..85f2f8833 100644 --- a/test/exec/test_timed_thread_scheduler.cpp +++ b/test/exec/test_timed_thread_scheduler.cpp @@ -22,7 +22,10 @@ #include #include -#if __GNUC__ > 11 || !defined(__GNUC__) || !defined(__SANITIZE_THREAD__) +// Avoid a TSAN bug in GCC 11 and earlier +#if STDEXEC_GCC() && STDEXEC_GCC_VERSION < 12'00 && defined(__SANITIZE_THREAD__) +// nothing +#else namespace { TEST_CASE( "timed_thread_scheduler - unused context", From 3d38032a2aaabce5cefe90f188dce0ee89b89508 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 3 Aug 2025 19:38:14 -0700 Subject: [PATCH 3/7] use a named concept for statically-querable types --- include/stdexec/__detail/__env.hpp | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index ca8253678..3ef1b32b0 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -377,6 +377,11 @@ namespace stdexec { template concept __nothrow_queryable = nothrow_tag_invocable<_Query, const _Env&, _Args...>; + template + concept __statically_queryable = requires { + std::remove_reference_t<_Env>::query(std::declval<_Query>(), std::declval<_Args>()...); + }; + template using __query_result_t = tag_invoke_result_t<_Query, const _Env&, _Args...>; @@ -457,15 +462,11 @@ namespace stdexec { // This is useful for constexpr evaluation of queries. template requires(__queryable<_Envs, _Query, _Args...> || ...) + && __statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> STDEXEC_ATTRIBUTE(nodiscard, always_inline) static constexpr auto query(_Query __q, _Args&&... __args) noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) - -> decltype(auto) - requires requires { - std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( - __q, static_cast<_Args &&>(__args)...); - } - { + -> decltype(auto) { return std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( __q, static_cast<_Args&&>(__args)...); } @@ -508,17 +509,12 @@ namespace stdexec { // NOT TO SPEC: a static query memfn for those envs that have a static query memfn. // This is useful for constexpr evaluation of queries. template - requires __queryable<_Env0, _Query, _Args...> - || __queryable<_Env1, _Query, _Args...> - STDEXEC_ATTRIBUTE(nodiscard, always_inline) - static constexpr auto query(_Query __q, _Args&&... __args) - noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) - -> decltype(auto) - requires requires { - std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( - __q, static_cast<_Args &&>(__args)...); - } - { + requires(__queryable<_Env0, _Query, _Args...> || __queryable<_Env1, _Query, _Args...>) + && __statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> + STDEXEC_ATTRIBUTE(nodiscard, always_inline) + static constexpr auto query(_Query __q, _Args&&... __args) + noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) + -> decltype(auto) { return std::remove_reference_t<__1st_env_t<_Query, _Args...>>::query( __q, static_cast<_Args&&>(__args)...); } From 18b2cc27302f9bcafd08c6447441d6dba462014d Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 3 Aug 2025 19:45:22 -0700 Subject: [PATCH 4/7] fix `__is_scheduler_affine` concept --- include/stdexec/__detail/__env.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 3ef1b32b0..ebcf16e71 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -817,7 +817,7 @@ namespace stdexec { template concept __is_scheduler_affine = requires { - requires env_of_t<_Sender>::query(__is_scheduler_affine_t{}); + requires std::remove_reference_t>::query(__is_scheduler_affine_t{}); }; } // namespace stdexec From 99047cfd2e95a3ff9ec761ee27be6176ee9d31b8 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 3 Aug 2025 20:33:33 -0700 Subject: [PATCH 5/7] work around concepts bug in earlier CDKs --- include/stdexec/__detail/__env.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index ebcf16e71..3e1db7d6b 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -378,7 +378,7 @@ namespace stdexec { concept __nothrow_queryable = nothrow_tag_invocable<_Query, const _Env&, _Args...>; template - concept __statically_queryable = requires { + concept __statically_queryable = __queryable<_Env, _Query, _Args...> && requires { std::remove_reference_t<_Env>::query(std::declval<_Query>(), std::declval<_Args>()...); }; @@ -461,8 +461,7 @@ namespace stdexec { // NOT TO SPEC: a static query memfn for those envs that have a static query memfn. // This is useful for constexpr evaluation of queries. template - requires(__queryable<_Envs, _Query, _Args...> || ...) - && __statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> + requires __statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> STDEXEC_ATTRIBUTE(nodiscard, always_inline) static constexpr auto query(_Query __q, _Args&&... __args) noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) @@ -472,7 +471,7 @@ namespace stdexec { } template - requires(__queryable<_Envs, _Query, _Args...> || ...) + requires __queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> STDEXEC_ATTRIBUTE(nodiscard, always_inline) constexpr auto query(_Query __q, _Args&&... __args) const noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) @@ -509,8 +508,7 @@ namespace stdexec { // NOT TO SPEC: a static query memfn for those envs that have a static query memfn. // This is useful for constexpr evaluation of queries. template - requires(__queryable<_Env0, _Query, _Args...> || __queryable<_Env1, _Query, _Args...>) - && __statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> + requires __statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> STDEXEC_ATTRIBUTE(nodiscard, always_inline) static constexpr auto query(_Query __q, _Args&&... __args) noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) @@ -520,7 +518,7 @@ namespace stdexec { } template - requires __queryable<_Env0, _Query, _Args...> || __queryable<_Env1, _Query, _Args...> + requires __queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> STDEXEC_ATTRIBUTE(nodiscard, always_inline) constexpr auto query(_Query __q, _Args&&... __args) const noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) From 497c3594ea65bfec5602aa629f49f6124f01dea0 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 3 Aug 2025 20:53:32 -0700 Subject: [PATCH 6/7] trying again --- include/stdexec/__detail/__env.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 3e1db7d6b..d779850bd 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -378,10 +378,14 @@ namespace stdexec { concept __nothrow_queryable = nothrow_tag_invocable<_Query, const _Env&, _Args...>; template - concept __statically_queryable = __queryable<_Env, _Query, _Args...> && requires { - std::remove_reference_t<_Env>::query(std::declval<_Query>(), std::declval<_Args>()...); + concept __statically_queryable_i = requires(_Query __q, _Args&&... __args) { + std::remove_reference_t<_Env>::query(__q, static_cast<_Args &&>(__args)...); }; + template + concept __statically_queryable = __queryable<_Env, _Query, _Args...> + && __statically_queryable_i<_Env, _Query, _Args...>; + template using __query_result_t = tag_invoke_result_t<_Query, const _Env&, _Args...>; From 2c50dee576c9893a27de882e021b5d57a91b1575 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 3 Aug 2025 21:23:58 -0700 Subject: [PATCH 7/7] once more --- include/stdexec/__detail/__env.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index d779850bd..f3066c499 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -476,6 +476,7 @@ namespace stdexec { template requires __queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> + && (!__statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) STDEXEC_ATTRIBUTE(nodiscard, always_inline) constexpr auto query(_Query __q, _Args&&... __args) const noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) @@ -523,6 +524,7 @@ namespace stdexec { template requires __queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...> + && (!__statically_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>) STDEXEC_ATTRIBUTE(nodiscard, always_inline) constexpr auto query(_Query __q, _Args&&... __args) const noexcept(__nothrow_queryable<__1st_env_t<_Query, _Args...>, _Query, _Args...>)