[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