[libcxx-commits] [libcxx] [libc++][execution] Implement C++26 std::execution set_value, set_error and set_stopped (PR #229095)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Oct 18 07:21:16 PDT 2026
https://github.com/emmett2020 updated https://github.com/llvm/llvm-project/pull/229095
>From 18acba18686e4cd6e20bdbe868bd311f7dc71ccb Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Mon, 5 Oct 2026 21:44:28 +0800
Subject: [PATCH 1/3] [libc++][execution] Implement C++26
std::execution::set_value, set_error, and set_stopped
---
libcxx/docs/Status/Cxx26Papers.csv | 2 +-
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__configuration/experimental.h | 1 +
.../__execution/completion_functions.h | 84 +++++++++++++
libcxx/include/execution | 13 ++
libcxx/include/module.modulemap.in | 1 +
libcxx/modules/std/execution.inc | 16 +++
.../exec.recv/completion_functions.verify.cpp | 59 +++++++++
.../exec.recv/completion_functions.pass.cpp | 117 ++++++++++++++++++
libcxx/utils/libcxx/test/params.py | 1 +
10 files changed, 294 insertions(+), 1 deletion(-)
create mode 100644 libcxx/include/__execution/completion_functions.h
create mode 100644 libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
create mode 100644 libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
diff --git a/libcxx/docs/Status/Cxx26Papers.csv b/libcxx/docs/Status/Cxx26Papers.csv
index 4ea28c789ae362..694fc22d13b73a 100644
--- a/libcxx/docs/Status/Cxx26Papers.csv
+++ b/libcxx/docs/Status/Cxx26Papers.csv
@@ -74,7 +74,7 @@
"`P2968R2 <https://wg21.link/P2968R2>`__","Make ``std::ignore`` a first-class object","2024-06 (St. Louis)","|Complete|","19","`#105436 <https://github.com/llvm/llvm-project/issues/105436>`__",""
"`P2075R6 <https://wg21.link/P2075R6>`__","Philox as an extension of the C++ RNG engines","2024-06 (St. Louis)","","","`#105437 <https://github.com/llvm/llvm-project/issues/105437>`__",""
"`P2422R1 <https://wg21.link/P2422R1>`__","Remove ``nodiscard`` annotations from the standard library specification","2024-06 (St. Louis)","|Complete|","19","`#105438 <https://github.com/llvm/llvm-project/issues/105438>`__","``nodiscard`` attributes were kept as a conforming extension"
-"`P2300R10 <https://wg21.link/P2300R10>`__","``std::execution``","2024-06 (St. Louis)","","","`#105440 <https://github.com/llvm/llvm-project/issues/105440>`__",""
+"`P2300R10 <https://wg21.link/P2300R10>`__","``std::execution``","2024-06 (St. Louis)","|Partial|","","`#105440 <https://github.com/llvm/llvm-project/issues/105440>`__",""
"","","","","","",""
"`P3136R1 <https://wg21.link/P3136R1>`__","Retiring niebloids","2024-11 (Wrocław)","|Complete|","14","`#118133 <https://github.com/llvm/llvm-project/issues/118133>`__",""
"`P3138R5 <https://wg21.link/P3138R5>`__","``views::cache_latest``","2024-11 (Wrocław)","","","`#118134 <https://github.com/llvm/llvm-project/issues/118134>`__",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index fe363130dc5cd5..b7c2aefbd6155c 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -367,6 +367,7 @@ set(files
__exception/nested_exception.h
__exception/operations.h
__exception/terminate.h
+ __execution/completion_functions.h
__execution/execution_policies.h
__execution/is_execution_policy.h
__expected/bad_expected_access.h
diff --git a/libcxx/include/__configuration/experimental.h b/libcxx/include/__configuration/experimental.h
index bb38d8297c63db..7445cc7eb3b64f 100644
--- a/libcxx/include/__configuration/experimental.h
+++ b/libcxx/include/__configuration/experimental.h
@@ -30,6 +30,7 @@
#endif
#define _LIBCPP_HAS_EXPERIMENTAL_PSTL _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
+#define _LIBCPP_HAS_EXPERIMENTAL_EXECUTION _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
#define _LIBCPP_HAS_EXPERIMENTAL_TZDB _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
#define _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
#define _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
diff --git a/libcxx/include/__execution/completion_functions.h b/libcxx/include/__execution/completion_functions.h
new file mode 100644
index 00000000000000..3e0e24a3c76a1c
--- /dev/null
+++ b/libcxx/include/__execution/completion_functions.h
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___EXECUTION_COMPLETION_FUNCTIONS_H
+#define _LIBCPP___EXECUTION_COMPLETION_FUNCTIONS_H
+
+#include <__config>
+#include <__type_traits/is_const.h>
+#include <__type_traits/is_reference.h>
+#include <__type_traits/is_same.h>
+#include <__utility/forward.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPERIMENTAL_EXECUTION
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace execution {
+
+// [exec.set.value]
+struct set_value_t {
+ template <class _Receiver, class... _Args>
+ requires(!is_lvalue_reference_v<_Receiver> && !is_const_v<_Receiver>) &&
+ requires(_Receiver&& __rcvr, _Args&&... __args) {
+ std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...);
+ }
+ _LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Receiver&& __rcvr, _Args&&... __args) const noexcept {
+ static_assert(noexcept(std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...)),
+ "set_value must be noexcept");
+ static_assert(is_same_v<decltype(std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...)), void>,
+ "set_value must return void");
+ std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...);
+ }
+};
+
+// [exec.set.error]
+struct set_error_t {
+ template <class _Receiver, class _Error>
+ requires(!is_lvalue_reference_v<_Receiver> && !is_const_v<_Receiver>) &&
+ requires(_Receiver&& __rcvr, _Error&& __error) {
+ std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error));
+ }
+ _LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Receiver&& __rcvr, _Error&& __error) const noexcept {
+ static_assert(noexcept(std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error))),
+ "set_error must be noexcept");
+ static_assert(is_same_v<decltype(std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error))), void>,
+ "set_error must return void");
+ std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error));
+ }
+};
+
+// [exec.set.stopped]
+struct set_stopped_t {
+ template <class _Receiver>
+ requires(!is_lvalue_reference_v<_Receiver> && !is_const_v<_Receiver>) && requires(_Receiver&& __rcvr) {
+ std::forward<_Receiver>(__rcvr).set_stopped();
+ }
+ _LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Receiver&& __rcvr) const noexcept {
+ static_assert(noexcept(std::forward<_Receiver>(__rcvr).set_stopped()), "set_stopped must be noexcept");
+ static_assert(
+ is_same_v<decltype(std::forward<_Receiver>(__rcvr).set_stopped()), void>, "set_stopped must return void");
+ std::forward<_Receiver>(__rcvr).set_stopped();
+ }
+};
+
+inline constexpr set_value_t set_value{};
+inline constexpr set_error_t set_error{};
+inline constexpr set_stopped_t set_stopped{};
+
+} // namespace execution
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPERIMENTAL_EXECUTION
+
+#endif // _LIBCPP___EXECUTION_COMPLETION_FUNCTIONS_H
diff --git a/libcxx/include/execution b/libcxx/include/execution
index 7940d18d66882c..94319d1018d1fd 100644
--- a/libcxx/include/execution
+++ b/libcxx/include/execution
@@ -23,6 +23,15 @@ namespace std::execution {
inline constexpr parallel_policy par = implementation-defined;
inline constexpr parallel_unsequenced_policy par_unseq = implementation-defined;
inline constexpr unsequenced_policy unseq = implementation-defined; // since C++20
+
+ // completion functions, since C++26
+ struct set_value_t;
+ struct set_error_t;
+ struct set_stopped_t;
+
+ inline constexpr set_value_t set_value{};
+ inline constexpr set_error_t set_error{};
+ inline constexpr set_stopped_t set_stopped{};
}
namespace std {
@@ -50,6 +59,10 @@ namespace std {
# include <__execution/is_execution_policy.h>
# endif
+# if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPERIMENTAL_EXECUTION
+# include <__execution/completion_functions.h>
+# endif
+
#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
#endif // _LIBCPP_EXECUTION
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 7c28ef1f8a9811..b6546741e99119 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1267,6 +1267,7 @@ module std {
module execution {
header "execution"
+ header "__execution/completion_functions.h"
header "__execution/execution_policies.h"
header "__execution/is_execution_policy.h"
export *
diff --git a/libcxx/modules/std/execution.inc b/libcxx/modules/std/execution.inc
index 86c1cd7622b9b8..928e0285f502e2 100644
--- a/libcxx/modules/std/execution.inc
+++ b/libcxx/modules/std/execution.inc
@@ -34,3 +34,19 @@ export namespace std::execution {
using std::execution::unseq;
} // namespace std::execution
#endif // _LIBCPP_ENABLE_EXPERIMENTAL
+
+#if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPERIMENTAL_EXECUTION
+export namespace std::execution {
+ // [exec.set.value], value completion function
+ using std::execution::set_value;
+ using std::execution::set_value_t;
+
+ // [exec.set.error], error completion function
+ using std::execution::set_error;
+ using std::execution::set_error_t;
+
+ // [exec.set.stopped], stopped completion function
+ using std::execution::set_stopped;
+ using std::execution::set_stopped_t;
+} // namespace std::execution
+#endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPERIMENTAL_EXECUTION
diff --git a/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp b/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
new file mode 100644
index 00000000000000..7ad34c3db9f15d
--- /dev/null
+++ b/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: libcpp-has-no-experimental-execution
+
+// The noexcept and void-return requirements are mandates, not constraints.
+
+#include <execution>
+
+namespace ex = std::execution;
+
+struct ThrowingReceiver {
+ void set_value() && noexcept(false);
+ void set_error(int) && noexcept(false);
+ void set_stopped() && noexcept(false);
+};
+
+struct NonVoidReceiver {
+ int set_value() && noexcept;
+ int set_error(int) && noexcept;
+ int set_stopped() && noexcept;
+};
+
+// Objects of this type can be converted to int, but the conversion may throw.
+struct ThrowingConversion {
+ operator int() noexcept(false);
+};
+
+struct ConversionReceiver {
+ void set_value(int) && noexcept;
+ void set_error(int) && noexcept;
+};
+
+void test() {
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_value must be noexcept}}
+ ex::set_value(ThrowingReceiver{});
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_error must be noexcept}}
+ ex::set_error(ThrowingReceiver{}, 0);
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_stopped must be noexcept}}
+ ex::set_stopped(ThrowingReceiver{});
+
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_value must return void}}
+ ex::set_value(NonVoidReceiver{});
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_error must return void}}
+ ex::set_error(NonVoidReceiver{}, 0);
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_stopped must return void}}
+ ex::set_stopped(NonVoidReceiver{});
+
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_value must be noexcept}}
+ ex::set_value(ConversionReceiver{}, ThrowingConversion{});
+ // expected-error-re@*:* {{static assertion failed{{.*}}set_error must be noexcept}}
+ ex::set_error(ConversionReceiver{}, ThrowingConversion{});
+}
diff --git a/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp b/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
new file mode 100644
index 00000000000000..f6cbb51b728ec4
--- /dev/null
+++ b/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
@@ -0,0 +1,117 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: libcpp-has-no-experimental-execution
+
+// [exec.set.value], [exec.set.error], [exec.set.stopped]
+
+#include <execution>
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+namespace ex = std::execution;
+
+static_assert(std::is_same_v<decltype(ex::set_value), const ex::set_value_t>);
+static_assert(std::is_same_v<decltype(ex::set_error), const ex::set_error_t>);
+static_assert(std::is_same_v<decltype(ex::set_stopped), const ex::set_stopped_t>);
+
+// Reject receivers that have no set_xxx members.
+struct NoMemberReceiver {};
+static_assert(!std::is_invocable_v<ex::set_value_t, NoMemberReceiver, int>);
+static_assert(!std::is_invocable_v<ex::set_error_t, NoMemberReceiver, int>);
+static_assert(!std::is_invocable_v<ex::set_stopped_t, NoMemberReceiver>);
+
+struct CvrefTestReceiver {
+ void set_value(int) const noexcept;
+ void set_error(int) const noexcept;
+ void set_stopped() const noexcept;
+};
+
+static_assert(std::is_nothrow_invocable_v<ex::set_value_t, CvrefTestReceiver, int>);
+static_assert(std::is_nothrow_invocable_v<ex::set_error_t, CvrefTestReceiver, int>);
+static_assert(std::is_nothrow_invocable_v<ex::set_stopped_t, CvrefTestReceiver>);
+
+static_assert(!std::is_invocable_v<ex::set_value_t, CvrefTestReceiver&, int>);
+static_assert(!std::is_invocable_v<ex::set_error_t, CvrefTestReceiver&, int>);
+static_assert(!std::is_invocable_v<ex::set_stopped_t, CvrefTestReceiver&>);
+
+static_assert(!std::is_invocable_v<ex::set_value_t, const CvrefTestReceiver, int>);
+static_assert(!std::is_invocable_v<ex::set_error_t, const CvrefTestReceiver, int>);
+static_assert(!std::is_invocable_v<ex::set_stopped_t, const CvrefTestReceiver>);
+
+struct StaticReceiver {
+ static void set_value(int) noexcept;
+ static void set_error(int) noexcept;
+ static void set_stopped() noexcept;
+};
+
+static_assert(std::is_nothrow_invocable_v<ex::set_value_t, StaticReceiver, int>);
+static_assert(std::is_nothrow_invocable_v<ex::set_error_t, StaticReceiver, int>);
+static_assert(std::is_nothrow_invocable_v<ex::set_stopped_t, StaticReceiver>);
+
+struct RvalueReceiver {
+ int& result;
+
+ constexpr void set_value() && noexcept { result = 0; }
+ constexpr void set_value(int value) && noexcept { result = value; }
+ constexpr void set_value(int& output, int&& value) && noexcept {
+ output = value;
+ result = value;
+ }
+ constexpr void set_error(int& error) && noexcept { result = error; }
+ constexpr void set_error(int&& error) && noexcept { result = error; }
+ constexpr void set_stopped() && noexcept { result = 0; }
+};
+
+// This receiver accepts at most two values, one error, and no stopped arguments.
+static_assert(!std::is_invocable_v<ex::set_value_t, RvalueReceiver, int, int, int>);
+static_assert(!std::is_invocable_v<ex::set_error_t, RvalueReceiver>);
+static_assert(!std::is_invocable_v<ex::set_error_t, RvalueReceiver, int, int>);
+static_assert(!std::is_invocable_v<ex::set_stopped_t, RvalueReceiver, int>);
+
+constexpr bool test() {
+ int result = -1;
+ RvalueReceiver receiver{result};
+ ex::set_value(std::move(receiver));
+ assert(result == 0);
+
+ result = -1;
+ ex::set_value(RvalueReceiver{result}, 42);
+ assert(result == 42);
+
+ // Forward multiple arguments, preserving lvalue and rvalue references.
+ int output = 0;
+ result = -1;
+ ex::set_value(RvalueReceiver{result}, output, 42);
+ assert(output == 42);
+ assert(result == 42);
+
+ result = -1;
+ ex::set_error(RvalueReceiver{result}, 42);
+ assert(result == 42);
+
+ int error = 42;
+ result = -1;
+ ex::set_error(RvalueReceiver{result}, error);
+ assert(result == 42);
+
+ result = -1;
+ ex::set_stopped(RvalueReceiver{result});
+ assert(result == 0);
+
+ return true;
+}
+
+int main(int, char**) {
+ test();
+ static_assert(test());
+ return 0;
+}
diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py
index 951b0a4dd9806e..9795121c1c18dc 100644
--- a/libcxx/utils/libcxx/test/params.py
+++ b/libcxx/utils/libcxx/test/params.py
@@ -366,6 +366,7 @@ def getSuitableClangTidy(cfg):
if experimental
else [
AddFeature("libcpp-has-no-incomplete-pstl"),
+ AddFeature("libcpp-has-no-experimental-execution"),
AddFeature("libcpp-has-no-experimental-optional-iterator"),
AddFeature("libcpp-has-no-experimental-tzdb"),
AddFeature("libcpp-has-no-experimental-syncstream"),
>From ec82fac9d015b42de24f5c9ece3f6c629d73b4bb Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Tue, 6 Oct 2026 12:50:11 +0800
Subject: [PATCH 2/3] fix some review comments
---
libcxx/include/execution | 13 +++----
.../exec.recv/completion_functions.verify.cpp | 35 +++++++++++++++++
.../exec.recv/completion_functions.pass.cpp | 38 +++++++++++++++++--
3 files changed, 76 insertions(+), 10 deletions(-)
diff --git a/libcxx/include/execution b/libcxx/include/execution
index 94319d1018d1fd..d5f128c555f77a 100644
--- a/libcxx/include/execution
+++ b/libcxx/include/execution
@@ -24,14 +24,13 @@ namespace std::execution {
inline constexpr parallel_unsequenced_policy par_unseq = implementation-defined;
inline constexpr unsequenced_policy unseq = implementation-defined; // since C++20
- // completion functions, since C++26
- struct set_value_t;
- struct set_error_t;
- struct set_stopped_t;
+ struct set_value_t; // Since C++26
+ struct set_error_t; // Since C++26
+ struct set_stopped_t; // Since C++26
- inline constexpr set_value_t set_value{};
- inline constexpr set_error_t set_error{};
- inline constexpr set_stopped_t set_stopped{};
+ inline constexpr set_value_t set_value{}; // Since C++26
+ inline constexpr set_error_t set_error{}; // Since C++26
+ inline constexpr set_stopped_t set_stopped{}; // Since C++26
}
namespace std {
diff --git a/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp b/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
index 7ad34c3db9f15d..567b63e26710f6 100644
--- a/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
+++ b/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
@@ -9,6 +9,41 @@
// REQUIRES: std-at-least-c++26
// UNSUPPORTED: libcpp-has-no-experimental-execution
+// <execution>
+//
+// namespace std::execution {
+// struct set_value_t {
+// template<class Receiver, class... Args>
+// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
+// requires(Receiver&& rcvr, Args&&... args) {
+// std::forward<Receiver>(rcvr).set_value(std::forward<Args>(args)...);
+// }
+// constexpr void operator()(Receiver&& rcvr, Args&&... args) const noexcept;
+// };
+//
+// struct set_error_t {
+// template<class Receiver, class Error>
+// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
+// requires(Receiver&& rcvr, Error&& error) {
+// std::forward<Receiver>(rcvr).set_error(std::forward<Error>(error));
+// }
+// constexpr void operator()(Receiver&& rcvr, Error&& error) const noexcept;
+// };
+//
+// struct set_stopped_t {
+// template<class Receiver>
+// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
+// requires(Receiver&& rcvr) {
+// std::forward<Receiver>(rcvr).set_stopped();
+// }
+// constexpr void operator()(Receiver&& rcvr) const noexcept;
+// };
+//
+// inline constexpr set_value_t set_value{};
+// inline constexpr set_error_t set_error{};
+// inline constexpr set_stopped_t set_stopped{};
+// }
+
// The noexcept and void-return requirements are mandates, not constraints.
#include <execution>
diff --git a/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp b/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
index f6cbb51b728ec4..a66d6a6521a7b4 100644
--- a/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
+++ b/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
@@ -9,11 +9,43 @@
// REQUIRES: std-at-least-c++26
// UNSUPPORTED: libcpp-has-no-experimental-execution
-// [exec.set.value], [exec.set.error], [exec.set.stopped]
-
-#include <execution>
+// <execution>
+//
+// namespace std::execution {
+// struct set_value_t {
+// template<class Receiver, class... Args>
+// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
+// requires(Receiver&& rcvr, Args&&... args) {
+// std::forward<Receiver>(rcvr).set_value(std::forward<Args>(args)...);
+// }
+// constexpr void operator()(Receiver&& rcvr, Args&&... args) const noexcept;
+// };
+//
+// struct set_error_t {
+// template<class Receiver, class Error>
+// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
+// requires(Receiver&& rcvr, Error&& error) {
+// std::forward<Receiver>(rcvr).set_error(std::forward<Error>(error));
+// }
+// constexpr void operator()(Receiver&& rcvr, Error&& error) const noexcept;
+// };
+//
+// struct set_stopped_t {
+// template<class Receiver>
+// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
+// requires(Receiver&& rcvr) {
+// std::forward<Receiver>(rcvr).set_stopped();
+// }
+// constexpr void operator()(Receiver&& rcvr) const noexcept;
+// };
+//
+// inline constexpr set_value_t set_value{};
+// inline constexpr set_error_t set_error{};
+// inline constexpr set_stopped_t set_stopped{};
+// }
#include <cassert>
+#include <execution>
#include <type_traits>
#include <utility>
>From 89b0eb74131058a17de7a36d7f7b7f3de948ea48 Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Sun, 18 Oct 2026 22:13:58 +0800
Subject: [PATCH 3/3] sync with our stdexec changes for std_xxxs
---
.../__execution/completion_functions.h | 36 ++++++++++-------
.../exec.recv/completion_functions.verify.cpp | 35 +++++++++++------
.../exec.recv/completion_functions.pass.cpp | 39 +++++++++++++------
3 files changed, 73 insertions(+), 37 deletions(-)
diff --git a/libcxx/include/__execution/completion_functions.h b/libcxx/include/__execution/completion_functions.h
index 3e0e24a3c76a1c..8371d112b18fa0 100644
--- a/libcxx/include/__execution/completion_functions.h
+++ b/libcxx/include/__execution/completion_functions.h
@@ -10,8 +10,6 @@
#define _LIBCPP___EXECUTION_COMPLETION_FUNCTIONS_H
#include <__config>
-#include <__type_traits/is_const.h>
-#include <__type_traits/is_reference.h>
#include <__type_traits/is_same.h>
#include <__utility/forward.h>
@@ -24,14 +22,31 @@
_LIBCPP_BEGIN_NAMESPACE_STD
namespace execution {
+namespace __detail {
+
+// For deduced forwarding-reference arguments, rejects lvalues and const rvalues.
+template <class _Tp>
+concept __non_const_rvalue = !is_same_v<_Tp, const _Tp>;
+
+template <class _Receiver, class... _Args>
+concept __set_value_member = requires(_Receiver&& __rcvr, _Args&&... __args) {
+ std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...);
+};
+
+template <class _Receiver, class _Error>
+concept __set_error_member = requires(_Receiver&& __rcvr, _Error&& __error) {
+ std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error));
+};
+
+template <class _Receiver>
+concept __set_stopped_member = requires(_Receiver&& __rcvr) { std::forward<_Receiver>(__rcvr).set_stopped(); };
+
+} // namespace __detail
// [exec.set.value]
struct set_value_t {
template <class _Receiver, class... _Args>
- requires(!is_lvalue_reference_v<_Receiver> && !is_const_v<_Receiver>) &&
- requires(_Receiver&& __rcvr, _Args&&... __args) {
- std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...);
- }
+ requires __detail::__non_const_rvalue<_Receiver> && __detail::__set_value_member<_Receiver, _Args...>
_LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Receiver&& __rcvr, _Args&&... __args) const noexcept {
static_assert(noexcept(std::forward<_Receiver>(__rcvr).set_value(std::forward<_Args>(__args)...)),
"set_value must be noexcept");
@@ -44,10 +59,7 @@ struct set_value_t {
// [exec.set.error]
struct set_error_t {
template <class _Receiver, class _Error>
- requires(!is_lvalue_reference_v<_Receiver> && !is_const_v<_Receiver>) &&
- requires(_Receiver&& __rcvr, _Error&& __error) {
- std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error));
- }
+ requires __detail::__non_const_rvalue<_Receiver> && __detail::__set_error_member<_Receiver, _Error>
_LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Receiver&& __rcvr, _Error&& __error) const noexcept {
static_assert(noexcept(std::forward<_Receiver>(__rcvr).set_error(std::forward<_Error>(__error))),
"set_error must be noexcept");
@@ -60,9 +72,7 @@ struct set_error_t {
// [exec.set.stopped]
struct set_stopped_t {
template <class _Receiver>
- requires(!is_lvalue_reference_v<_Receiver> && !is_const_v<_Receiver>) && requires(_Receiver&& __rcvr) {
- std::forward<_Receiver>(__rcvr).set_stopped();
- }
+ requires __detail::__non_const_rvalue<_Receiver> && __detail::__set_stopped_member<_Receiver>
_LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Receiver&& __rcvr) const noexcept {
static_assert(noexcept(std::forward<_Receiver>(__rcvr).set_stopped()), "set_stopped must be noexcept");
static_assert(
diff --git a/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp b/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
index 567b63e26710f6..df9793f763af97 100644
--- a/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
+++ b/libcxx/test/libcxx/exec/exec.recv/completion_functions.verify.cpp
@@ -12,30 +12,41 @@
// <execution>
//
// namespace std::execution {
+// namespace __detail {
+// template<class T>
+// concept __non_const_rvalue = !is_same_v<T, const T>;
+//
+// template<class Receiver, class... Args>
+// concept __set_value_member = requires(Receiver&& rcvr, Args&&... args) {
+// std::forward<Receiver>(rcvr).set_value(std::forward<Args>(args)...);
+// };
+//
+// template<class Receiver, class Error>
+// concept __set_error_member = requires(Receiver&& rcvr, Error&& error) {
+// std::forward<Receiver>(rcvr).set_error(std::forward<Error>(error));
+// };
+//
+// template<class Receiver>
+// concept __set_stopped_member = requires(Receiver&& rcvr) {
+// std::forward<Receiver>(rcvr).set_stopped();
+// };
+// } // namespace __detail
+//
// struct set_value_t {
// template<class Receiver, class... Args>
-// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
-// requires(Receiver&& rcvr, Args&&... args) {
-// std::forward<Receiver>(rcvr).set_value(std::forward<Args>(args)...);
-// }
+// requires __detail::__non_const_rvalue<Receiver> && __detail::__set_value_member<Receiver, Args...>
// constexpr void operator()(Receiver&& rcvr, Args&&... args) const noexcept;
// };
//
// struct set_error_t {
// template<class Receiver, class Error>
-// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
-// requires(Receiver&& rcvr, Error&& error) {
-// std::forward<Receiver>(rcvr).set_error(std::forward<Error>(error));
-// }
+// requires __detail::__non_const_rvalue<Receiver> && __detail::__set_error_member<Receiver, Error>
// constexpr void operator()(Receiver&& rcvr, Error&& error) const noexcept;
// };
//
// struct set_stopped_t {
// template<class Receiver>
-// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
-// requires(Receiver&& rcvr) {
-// std::forward<Receiver>(rcvr).set_stopped();
-// }
+// requires __detail::__non_const_rvalue<Receiver> && __detail::__set_stopped_member<Receiver>
// constexpr void operator()(Receiver&& rcvr) const noexcept;
// };
//
diff --git a/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp b/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
index a66d6a6521a7b4..33f2c1c85e79f6 100644
--- a/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
+++ b/libcxx/test/std/exec/exec.recv/completion_functions.pass.cpp
@@ -12,30 +12,41 @@
// <execution>
//
// namespace std::execution {
+// namespace __detail {
+// template<class T>
+// concept __non_const_rvalue = !is_same_v<T, const T>;
+//
+// template<class Receiver, class... Args>
+// concept __set_value_member = requires(Receiver&& rcvr, Args&&... args) {
+// std::forward<Receiver>(rcvr).set_value(std::forward<Args>(args)...);
+// };
+//
+// template<class Receiver, class Error>
+// concept __set_error_member = requires(Receiver&& rcvr, Error&& error) {
+// std::forward<Receiver>(rcvr).set_error(std::forward<Error>(error));
+// };
+//
+// template<class Receiver>
+// concept __set_stopped_member = requires(Receiver&& rcvr) {
+// std::forward<Receiver>(rcvr).set_stopped();
+// };
+// } // namespace __detail
+//
// struct set_value_t {
// template<class Receiver, class... Args>
-// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
-// requires(Receiver&& rcvr, Args&&... args) {
-// std::forward<Receiver>(rcvr).set_value(std::forward<Args>(args)...);
-// }
+// requires __detail::__non_const_rvalue<Receiver> && __detail::__set_value_member<Receiver, Args...>
// constexpr void operator()(Receiver&& rcvr, Args&&... args) const noexcept;
// };
//
// struct set_error_t {
// template<class Receiver, class Error>
-// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
-// requires(Receiver&& rcvr, Error&& error) {
-// std::forward<Receiver>(rcvr).set_error(std::forward<Error>(error));
-// }
+// requires __detail::__non_const_rvalue<Receiver> && __detail::__set_error_member<Receiver, Error>
// constexpr void operator()(Receiver&& rcvr, Error&& error) const noexcept;
// };
//
// struct set_stopped_t {
// template<class Receiver>
-// requires (!is_lvalue_reference_v<Receiver> && !is_const_v<Receiver>) &&
-// requires(Receiver&& rcvr) {
-// std::forward<Receiver>(rcvr).set_stopped();
-// }
+// requires __detail::__non_const_rvalue<Receiver> && __detail::__set_stopped_member<Receiver>
// constexpr void operator()(Receiver&& rcvr) const noexcept;
// };
//
@@ -71,6 +82,10 @@ static_assert(std::is_nothrow_invocable_v<ex::set_value_t, CvrefTestReceiver, in
static_assert(std::is_nothrow_invocable_v<ex::set_error_t, CvrefTestReceiver, int>);
static_assert(std::is_nothrow_invocable_v<ex::set_stopped_t, CvrefTestReceiver>);
+static_assert(std::is_same_v<std::invoke_result_t<ex::set_value_t, CvrefTestReceiver, int>, void>);
+static_assert(std::is_same_v<std::invoke_result_t<ex::set_error_t, CvrefTestReceiver, int>, void>);
+static_assert(std::is_same_v<std::invoke_result_t<ex::set_stopped_t, CvrefTestReceiver>, void>);
+
static_assert(!std::is_invocable_v<ex::set_value_t, CvrefTestReceiver&, int>);
static_assert(!std::is_invocable_v<ex::set_error_t, CvrefTestReceiver&, int>);
static_assert(!std::is_invocable_v<ex::set_stopped_t, CvrefTestReceiver&>);
More information about the libcxx-commits
mailing list