diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp
index 8539d45ca..1e91584ff 100644
--- a/include/stdexec/__detail/__config.hpp
+++ b/include/stdexec/__detail/__config.hpp
@@ -617,6 +617,12 @@ namespace STDEXEC
# define STDEXEC_NO_STDCPP_PACK_INDEXING() 1
#endif // no pack indexing
+#if __cpp_impl_reflection >= 202506L && __cpp_lib_reflection >= 202506L
+# define STDEXEC_NO_STDCPP_REFLECTION() 0
+#else
+# define STDEXEC_NO_STDCPP_REFLECTION() 1
+#endif
+
#if STDEXEC_HAS_FEATURE(thread_sanitizer) || defined(__SANITIZE_THREAD__)
# define STDEXEC_TSAN() 1
#else
diff --git a/include/stdexec/__detail/__get_completion_signatures.hpp b/include/stdexec/__detail/__get_completion_signatures.hpp
index b9f55229e..1b3f0f2b0 100644
--- a/include/stdexec/__detail/__get_completion_signatures.hpp
+++ b/include/stdexec/__detail/__get_completion_signatures.hpp
@@ -35,6 +35,10 @@ import stdexec;
# include "__tag_invoke.hpp"
# include "__tuple.hpp" // IWYU pragma: keep for __tuple
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+# include
+# endif
+
# include "__prologue.hpp"
namespace STDEXEC
@@ -62,6 +66,16 @@ namespace STDEXEC
struct _A_GET_COMPLETION_SIGNATURES_CUSTOMIZATION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION;
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+ STDEXEC_MODULE_EXPORT
+ template
+ consteval std::meta::info get_completion_signatures_type();
+
+ STDEXEC_MODULE_EXPORT
+ template
+ consteval std::meta::info get_completion_signatures_type();
+# endif
+
namespace __cmplsigs
{
# define STDEXEC_GET_COMPLSIGS(...) \
@@ -162,6 +176,28 @@ namespace STDEXEC
template
concept __with_co_await = __awaitable<_Sender, __detail::__promise<_Env>...>;
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+ template
+ concept __has_get_completion_signatures_type = requires {
+ {
+ STDEXEC_REMOVE_REFERENCE(_Sender)
+ ::template get_completion_signatures_type<_Sender, _Env...>()
+ } -> __std::same_as;
+ };
+
+ template
+ consteval bool __nothrow_get_completion_signatures_type() noexcept
+ try
+ {
+ (void) STDEXEC::get_completion_signatures_type<_Sender, _Env...>();
+ return true;
+ }
+ catch (...)
+ {
+ return false;
+ }
+# endif
+
template
concept __with = __with_legacy_static_member<_Sender, _Env> //
|| __with_legacy_member<_Sender, _Env> //
@@ -170,7 +206,11 @@ namespace STDEXEC
|| __with_consteval_static_member<_Sender> //
|| __with_legacy_tag_invoke<_Sender, _Env> //
|| __with_legacy_non_dependent_tag_invoke<_Sender, _Env> //
- || __with_co_await<_Sender, _Env>;
+ || __with_co_await<_Sender, _Env>
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+ || __has_get_completion_signatures_type<_Sender, _Env>
+# endif
+ ;
} // namespace __cmplsigs
template
@@ -354,7 +394,26 @@ namespace STDEXEC
template
consteval auto get_completion_signatures()
{
- return __cmplsigs::__get_completion_signatures_helper<_Sender>();
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+ if constexpr (__cmplsigs::__has_get_completion_signatures_type<_Sender>)
+ {
+ if constexpr (__cmplsigs::__nothrow_get_completion_signatures_type<_Sender>())
+ {
+ constexpr std::meta::info __completions =
+ STDEXEC::get_completion_signatures_type<_Sender>();
+ return typename[:__completions:]{};
+ }
+ else
+ {
+ (void) STDEXEC::get_completion_signatures_type<_Sender>();
+ return completion_signatures<>{};
+ }
+ }
+ else
+# endif
+ {
+ return __cmplsigs::__get_completion_signatures_helper<_Sender>();
+ }
}
//! @brief Overload of @ref get_completion_signatures that takes an
@@ -377,9 +436,63 @@ namespace STDEXEC
{
using __new_sndr_t = transform_sender_result_t<_Sender, _Env>;
static_assert(!__merror<__new_sndr_t>);
- return __cmplsigs::__get_completion_signatures_helper<__new_sndr_t, _Env>();
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+ if constexpr (__cmplsigs::__has_get_completion_signatures_type<__new_sndr_t, _Env>)
+ {
+ if constexpr (__cmplsigs::__nothrow_get_completion_signatures_type<_Sender, _Env>())
+ {
+ constexpr std::meta::info __completions =
+ STDEXEC::get_completion_signatures_type<_Sender, _Env>();
+ return typename[:__completions:]{};
+ }
+ else
+ {
+ (void) STDEXEC::get_completion_signatures_type<_Sender, _Env>();
+ return completion_signatures<>{};
+ }
+ }
+ else
+# endif
+ {
+ return __cmplsigs::__get_completion_signatures_helper<__new_sndr_t, _Env>();
+ }
}
+# if !STDEXEC_NO_STDCPP_REFLECTION()
+ STDEXEC_MODULE_EXPORT
+ template
+ consteval std::meta::info get_completion_signatures_type()
+ {
+ if constexpr (__cmplsigs::__has_get_completion_signatures_type<_Sender>)
+ {
+ return STDEXEC_REMOVE_REFERENCE(_Sender)::template get_completion_signatures_type<_Sender>();
+ }
+ else
+ {
+ auto __completions = STDEXEC::get_completion_signatures<_Sender>();
+ return ^^decltype(__completions);
+ }
+ }
+
+ STDEXEC_MODULE_EXPORT
+ template
+ consteval std::meta::info get_completion_signatures_type()
+ {
+ using __new_sndr_t = transform_sender_result_t<_Sender, _Env>;
+ static_assert(!__merror<__new_sndr_t>);
+ if constexpr (__cmplsigs::__has_get_completion_signatures_type<__new_sndr_t, _Env>)
+ {
+ return STDEXEC_REMOVE_REFERENCE(
+ __new_sndr_t)::template get_completion_signatures_type<__new_sndr_t, _Env>();
+ }
+ else
+ {
+ auto __completions = STDEXEC::get_completion_signatures<_Sender, _Env>();
+ return ^^decltype(__completions);
+ }
+ }
+# endif
+
// Legacy interface:
STDEXEC_MODULE_EXPORT_AUTHORING
template
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 2725a8fba..ba57f7a8b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -68,6 +68,7 @@ set(stdexec_test_sources
stdexec/detail/test_common_domain.cpp
stdexec/detail/test_completion_signatures.cpp
stdexec/detail/test_demangle.cpp
+ stdexec/detail/test_get_completion_signatures.cpp
stdexec/detail/test_intrusive_mpsc_queue.cpp
stdexec/detail/test_utility.cpp
stdexec/queries/test_env.cpp
diff --git a/test/stdexec/detail/test_get_completion_signatures.cpp b/test/stdexec/detail/test_get_completion_signatures.cpp
new file mode 100644
index 000000000..0d17ea2ca
--- /dev/null
+++ b/test/stdexec/detail/test_get_completion_signatures.cpp
@@ -0,0 +1,248 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+ *
+ * Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://llvm.org/LICENSE.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+
+#include
+
+#if !STDEXEC_NO_STDCPP_REFLECTION()
+# include
+#endif
+
+namespace ex = STDEXEC;
+
+namespace
+{
+ using independent_completions = ex::completion_signatures;
+ using dependent_completions = ex::completion_signatures;
+
+ struct test_sender
+ {
+ using sender_concept = ex::sender_tag;
+
+ template
+ static consteval auto get_completion_signatures() noexcept
+ {
+ if constexpr (sizeof...(Env) == 0)
+ {
+ return independent_completions{};
+ }
+ else
+ {
+ return dependent_completions{};
+ }
+ }
+ };
+
+#if !STDEXEC_NO_STDCPP_REFLECTION()
+ template
+ consteval bool completion_signature_protocols_agree()
+ {
+ return std::meta::is_same_type(ex::get_completion_signatures_type(),
+ ^^decltype(ex::get_completion_signatures()));
+ }
+
+ template
+ consteval bool get_completion_signatures_type_throws() noexcept
+ try
+ {
+ (void) ex::get_completion_signatures_type();
+ return false;
+ }
+ catch (...)
+ {
+ return true;
+ }
+
+ struct throwing_legacy_test_sender
+ {
+ using sender_concept = ex::sender_tag;
+
+ template
+ static consteval auto get_completion_signatures()
+ {
+ throw std::meta::exception("Unable to compute completion signatures.",
+ ^^throwing_legacy_test_sender);
+ return ex::completion_signatures<>{};
+ }
+ };
+
+ struct reflection_test_sender
+ {
+ using sender_concept = ex::sender_tag;
+
+ template
+ static consteval std::meta::info get_completion_signatures_type() noexcept
+ {
+ if constexpr (sizeof...(Env) == 0)
+ {
+ return ^^independent_completions;
+ }
+ else
+ {
+ return ^^dependent_completions;
+ }
+ }
+ };
+
+ struct transformed_reflection_test_sender
+ {
+ using sender_concept = ex::sender_tag;
+
+ template
+ static consteval std::meta::info get_completion_signatures_type() noexcept
+ {
+ return ^^dependent_completions;
+ }
+ };
+
+ struct throwing_reflection_test_sender
+ {
+ using sender_concept = ex::sender_tag;
+
+ template
+ static consteval std::meta::info get_completion_signatures_type()
+ {
+ throw std::meta::exception("Unable to compute completion signatures.",
+ ^^throwing_reflection_test_sender);
+ }
+ };
+
+ struct reflection_test_domain
+ {
+ template
+ auto transform_sender(ex::start_t, reflection_test_sender, Env const &) const
+ -> transformed_reflection_test_sender
+ {
+ return {};
+ }
+ };
+
+ using reflection_test_env = ex::prop;
+
+ struct throwing_reflection_test_domain
+ {
+ template
+ auto transform_sender(ex::start_t, reflection_test_sender, Env const &) const
+ -> throwing_reflection_test_sender
+ {
+ return {};
+ }
+ };
+
+ using throwing_reflection_test_env = ex::prop;
+#endif
+
+ TEST_CASE("get_completion_signatures queries a sender without an environment",
+ "[detail][get_completion_signatures]")
+ {
+ STATIC_REQUIRE(std::same_as()),
+ independent_completions>);
+#if !STDEXEC_NO_STDCPP_REFLECTION()
+ STATIC_REQUIRE(completion_signature_protocols_agree());
+#endif
+ }
+
+ TEST_CASE("get_completion_signatures queries a sender in an environment",
+ "[detail][get_completion_signatures]")
+ {
+ STATIC_REQUIRE(std::same_as>()),
+ dependent_completions>);
+#if !STDEXEC_NO_STDCPP_REFLECTION()
+ STATIC_REQUIRE(completion_signature_protocols_agree>());
+#endif
+ }
+
+ TEST_CASE("get_completion_signatures supports the legacy function-call interface",
+ "[detail][get_completion_signatures]")
+ {
+ STATIC_REQUIRE(std::same_as{})),
+ dependent_completions>);
+#if !STDEXEC_NO_STDCPP_REFLECTION()
+ STATIC_REQUIRE(completion_signature_protocols_agree>());
+#endif
+ }
+
+#if !STDEXEC_NO_STDCPP_REFLECTION()
+ TEST_CASE("get_completion_signatures_type reflects the legacy query result",
+ "[detail][get_completion_signatures_type][reflection]")
+ {
+ STATIC_REQUIRE(std::meta::is_same_type(ex::get_completion_signatures_type(),
+ ^^independent_completions));
+ STATIC_REQUIRE(
+ std::meta::is_same_type(ex::get_completion_signatures_type>(),
+ ^^dependent_completions));
+ STATIC_REQUIRE(completion_signature_protocols_agree());
+ STATIC_REQUIRE(completion_signature_protocols_agree>());
+ }
+
+ TEST_CASE("get_completion_signatures_type invokes the legacy query",
+ "[detail][get_completion_signatures_type][reflection]")
+ {
+ STATIC_REQUIRE(get_completion_signatures_type_throws());
+ STATIC_REQUIRE(get_completion_signatures_type_throws>());
+ }
+
+ TEST_CASE("detect whether get_completion_signatures_type throws",
+ "[detail][get_completion_signatures_type][reflection]")
+ {
+ STATIC_REQUIRE(
+ ex::__cmplsigs::__nothrow_get_completion_signatures_type());
+ STATIC_REQUIRE_FALSE(
+ ex::__cmplsigs::__nothrow_get_completion_signatures_type());
+ STATIC_REQUIRE_FALSE(
+ ex::__cmplsigs::__nothrow_get_completion_signatures_type());
+ STATIC_REQUIRE(
+ std::same_as()),
+ ex::completion_signatures<>>);
+ STATIC_REQUIRE(
+ std::same_as()),
+ ex::completion_signatures<>>);
+ }
+
+ TEST_CASE("get_completion_signatures_type queries a reflection-native sender without an "
+ "environment",
+ "[detail][get_completion_signatures_type][reflection]")
+ {
+ STATIC_REQUIRE(
+ std::meta::is_same_type(ex::get_completion_signatures_type(),
+ ^^independent_completions));
+ STATIC_REQUIRE(completion_signature_protocols_agree());
+ }
+
+ TEST_CASE("get_completion_signatures_type queries a reflection-native sender in an environment",
+ "[detail][get_completion_signatures_type][reflection]")
+ {
+ STATIC_REQUIRE(std::meta::is_same_type(
+ ex::get_completion_signatures_type>(),
+ ^^dependent_completions));
+ STATIC_REQUIRE(completion_signature_protocols_agree>());
+ }
+
+ TEST_CASE("get_completion_signatures_type queries the transformed sender",
+ "[detail][get_completion_signatures_type][reflection]")
+ {
+ STATIC_REQUIRE(std::meta::is_same_type(
+ ex::get_completion_signatures_type(),
+ ^^dependent_completions));
+ STATIC_REQUIRE(
+ completion_signature_protocols_agree());
+ }
+#endif
+} // namespace