[libcxx-commits] [libcxx] [libc++] Implement LWG3476: Add mandates to thread, jthread, and async (PR #173363)
Marcell Leleszi via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Dec 23 04:46:19 PST 2025
https://github.com/mleleszi updated https://github.com/llvm/llvm-project/pull/173363
>From 376e73497074b14582a113776519abbb2b726258 Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Sun, 21 Dec 2025 20:10:53 +0100
Subject: [PATCH 1/3] Add mandates for thread
---
libcxx/include/__thread/thread.h | 8 +++
libcxx/include/future | 7 +++
.../futures/futures.async/async.verify.cpp | 15 +++++
.../thread.thread.constr/F.pass.cpp | 14 +++++
.../thread.thread.constr/F.verify.cpp | 63 +++++++++++++++++++
5 files changed, 107 insertions(+)
create mode 100644 libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
diff --git a/libcxx/include/__thread/thread.h b/libcxx/include/__thread/thread.h
index 561f092ddb7c0..24b4caa7f0d2f 100644
--- a/libcxx/include/__thread/thread.h
+++ b/libcxx/include/__thread/thread.h
@@ -25,6 +25,8 @@
#include <__thread/support.h>
#include <__type_traits/decay.h>
#include <__type_traits/enable_if.h>
+#include <__type_traits/invoke.h>
+#include <__type_traits/is_constructible.h>
#include <__type_traits/is_same.h>
#include <__type_traits/remove_cvref.h>
#include <__utility/forward.h>
@@ -205,6 +207,12 @@ class _LIBCPP_EXPORTED_FROM_ABI thread {
# ifndef _LIBCPP_CXX03_LANG
template <class _Fp, class... _Args, __enable_if_t<!is_same<__remove_cvref_t<_Fp>, thread>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI explicit thread(_Fp&& __f, _Args&&... __args) {
+# if _LIBCPP_STD_VER >= 17
+ static_assert(is_constructible_v<__decay_t<_Fp>, _Fp>);
+ static_assert((is_constructible_v<__decay_t<_Args>, _Args> && ...));
+ static_assert(is_invocable_v<__decay_t<_Fp>, __decay_t<_Args>...>);
+# endif
+
typedef unique_ptr<__thread_struct> _TSPtr;
_TSPtr __tsp(new __thread_struct);
typedef tuple<_TSPtr, __decay_t<_Fp>, __decay_t<_Args>...> _Gp;
diff --git a/libcxx/include/future b/libcxx/include/future
index c249bc5e7938f..4260068af268f 100644
--- a/libcxx/include/future
+++ b/libcxx/include/future
@@ -397,6 +397,7 @@ template <class R, class Alloc> struct uses_allocator<packaged_task<R>, Alloc>;
# include <__type_traits/decay.h>
# include <__type_traits/enable_if.h>
# include <__type_traits/invoke.h>
+# include <__type_traits/is_constructible.h>
# include <__type_traits/is_same.h>
# include <__type_traits/remove_cvref.h>
# include <__type_traits/remove_reference.h>
@@ -1847,6 +1848,12 @@ inline _LIBCPP_HIDE_FROM_ABI bool __does_policy_contain(launch __policy, launch
template <class _Fp, class... _Args>
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI future<__invoke_result_t<__decay_t<_Fp>, __decay_t<_Args>...> >
async(launch __policy, _Fp&& __f, _Args&&... __args) {
+# if _LIBCPP_STD_VER >= 17
+ static_assert(is_constructible_v<__decay_t<_Fp>, _Fp>);
+ static_assert((is_constructible_v<__decay_t<_Args>, _Args> && ...));
+ static_assert(is_invocable_v<__decay_t<_Fp>, __decay_t<_Args>...>);
+# endif
+
typedef __async_func<__decay_t<_Fp>, __decay_t<_Args>...> _BF;
typedef typename _BF::_Rp _Rp;
diff --git a/libcxx/test/std/thread/futures/futures.async/async.verify.cpp b/libcxx/test/std/thread/futures/futures.async/async.verify.cpp
index e47ad82e79980..6b686cd9a1747 100644
--- a/libcxx/test/std/thread/futures/futures.async/async.verify.cpp
+++ b/libcxx/test/std/thread/futures/futures.async/async.verify.cpp
@@ -27,4 +27,19 @@ int foo (int x) { return x; }
void f() {
std::async( foo, 3); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::async(std::launch::async, foo, 3); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+ { // Mandates: is_constructible_v<decay_t<F>, F>
+ struct F {
+ F() = default;
+ F(const F&) = default;
+ F(F&&) = delete;
+
+ void operator()() {}
+ };
+
+ static_cast<void>(std::async(F{}));
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{call to deleted constructor}}
+ // expected-error@*:* 1+ {{uses deleted function}}
+ }
}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
index 1c520652ba229..363f7c14559a8 100644
--- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
@@ -103,6 +103,15 @@ class MoveOnly
}
};
+class CopyOnly {
+public:
+ CopyOnly() {}
+ CopyOnly(const CopyOnly&) {}
+ CopyOnly(CopyOnly&&) = delete;
+
+ void operator()(const CopyOnly&) const {}
+};
+
#endif
// Test throwing std::bad_alloc
@@ -212,6 +221,11 @@ int main(int, char**)
std::thread t = std::thread(MoveOnly(), MoveOnly());
t.join();
}
+ {
+ CopyOnly c;
+ std::thread t(c, c);
+ t.join();
+ }
#endif
return 0;
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
new file mode 100644
index 0000000000000..1b8b8138f8066
--- /dev/null
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: c++03, c++11, c++14
+
+// <thread>
+
+// class thread
+
+// template<class F, class... Args>
+// explicit thread(F&& f, Args&&... args);
+
+#include <thread>
+
+void test() {
+ { // Mandates: is_constructible_v<decay_t<F>, F>
+ struct F {
+ F() = default;
+ F(const F&) = delete;
+ F(F&&) = delete;
+
+ void operator()() {}
+ };
+
+ F f;
+ std::thread t(f);
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+ struct Arg {
+ Arg() = default;
+ Arg(const Arg&) = delete;
+ Arg(Arg&&) = delete;
+ };
+
+ struct F {
+ void operator()(const Arg&) const {}
+ };
+
+ Arg arg;
+ std::thread t(F{}, arg);
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* 0+ {{call to deleted constructor}}
+ // expected-error@*:* 0+ {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: is_invocable_v<decay_t<F>, decay_t<Args>...>
+ struct F {};
+
+ std::thread t(F{});
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{no matching function for call to '__invoke'}}
+ }
+}
>From 913bcd57451d0c2ef824246db95f60fd329de279 Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Mon, 22 Dec 2025 12:13:48 +0100
Subject: [PATCH 2/3] Implement LWG4376
---
libcxx/docs/Status/Cxx23Issues.csv | 2 +-
libcxx/include/__thread/jthread.h | 12 +--
libcxx/include/future | 11 +--
.../futures/futures.async/async.pass.cpp | 25 +++++
.../futures/futures.async/async.verify.cpp | 58 ++++++++++-
.../thread.jthread/cons.func.token.pass.cpp | 25 +++++
.../std/thread/thread.jthread/cons.verify.cpp | 95 +++++++++++++++++++
.../thread.thread.constr/F.pass.cpp | 4 +-
.../thread.thread.constr/F.verify.cpp | 40 +++++++-
9 files changed, 252 insertions(+), 20 deletions(-)
create mode 100644 libcxx/test/std/thread/thread.jthread/cons.verify.cpp
diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv
index 389e1ad254a74..ef48759172382 100644
--- a/libcxx/docs/Status/Cxx23Issues.csv
+++ b/libcxx/docs/Status/Cxx23Issues.csv
@@ -45,7 +45,7 @@
"`LWG3472 <https://wg21.link/LWG3472>`__","``counted_iterator`` is missing preconditions","2020-11 (Virtual)","|Complete|","14","`#104304 <https://github.com/llvm/llvm-project/issues/104304>`__",""
"`LWG3473 <https://wg21.link/LWG3473>`__","Normative encouragement in non-normative note","2020-11 (Virtual)","|Complete|","15","`#104305 <https://github.com/llvm/llvm-project/issues/104305>`__",""
"`LWG3474 <https://wg21.link/LWG3474>`__","Nesting ``join_views`` is broken because of CTAD","2020-11 (Virtual)","|Complete|","15","`#104306 <https://github.com/llvm/llvm-project/issues/104306>`__",""
-"`LWG3476 <https://wg21.link/LWG3476>`__","``thread`` and ``jthread`` constructors require that the parameters be move-constructible but never move construct the parameters","2020-11 (Virtual)","","","`#104307 <https://github.com/llvm/llvm-project/issues/104307>`__",""
+"`LWG3476 <https://wg21.link/LWG3476>`__","``thread`` and ``jthread`` constructors require that the parameters be move-constructible but never move construct the parameters","2020-11 (Virtual)","|Complete|","","`#104307 <https://github.com/llvm/llvm-project/issues/104307>`__",""
"`LWG3477 <https://wg21.link/LWG3477>`__","Simplify constraints for ``semiregular-box``","2020-11 (Virtual)","|Complete|","13","`#104308 <https://github.com/llvm/llvm-project/issues/104308>`__",""
"`LWG3482 <https://wg21.link/LWG3482>`__","``drop_view``'s const begin should additionally require ``sized_range``","2020-11 (Virtual)","|Complete|","14","`#104309 <https://github.com/llvm/llvm-project/issues/104309>`__",""
"`LWG3483 <https://wg21.link/LWG3483>`__","``transform_view::iterator``'s difference is overconstrained","2020-11 (Virtual)","|Complete|","14","`#104310 <https://github.com/llvm/llvm-project/issues/104310>`__",""
diff --git a/libcxx/include/__thread/jthread.h b/libcxx/include/__thread/jthread.h
index 481ffe296c271..632e33bcfd447 100644
--- a/libcxx/include/__thread/jthread.h
+++ b/libcxx/include/__thread/jthread.h
@@ -49,12 +49,7 @@ class jthread {
_LIBCPP_HIDE_FROM_ABI explicit jthread(_Fun&& __fun, _Args&&... __args)
requires(!std::is_same_v<remove_cvref_t<_Fun>, jthread>)
: __stop_source_(),
- __thread_(__init_thread(__stop_source_, std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) {
- static_assert(is_constructible_v<decay_t<_Fun>, _Fun>);
- static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...));
- static_assert(is_invocable_v<decay_t<_Fun>, decay_t<_Args>...> ||
- is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>);
- }
+ __thread_(__init_thread(__stop_source_, std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) {}
_LIBCPP_HIDE_FROM_ABI ~jthread() {
if (joinable()) {
@@ -116,6 +111,11 @@ class jthread {
private:
template <class _Fun, class... _Args>
_LIBCPP_HIDE_FROM_ABI static thread __init_thread(const stop_source& __ss, _Fun&& __fun, _Args&&... __args) {
+ static_assert(is_constructible_v<decay_t<_Fun>, _Fun>);
+ static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...));
+ static_assert(is_invocable_v<decay_t<_Fun>, decay_t<_Args>...> ||
+ is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>);
+
if constexpr (is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>) {
return thread(std::forward<_Fun>(__fun), __ss.get_token(), std::forward<_Args>(__args)...);
} else {
diff --git a/libcxx/include/future b/libcxx/include/future
index 4260068af268f..91c870df5b07b 100644
--- a/libcxx/include/future
+++ b/libcxx/include/future
@@ -1829,8 +1829,9 @@ class _LIBCPP_HIDDEN __async_func {
public:
using _Rp _LIBCPP_NODEBUG = __invoke_result_t<_Fp, _Args...>;
- _LIBCPP_HIDE_FROM_ABI explicit __async_func(_Fp&& __f, _Args&&... __args)
- : __f_(std::move(__f), std::move(__args)...) {}
+ template <class _Gp, class... _BArgs>
+ _LIBCPP_HIDE_FROM_ABI explicit __async_func(_Gp&& __g, _BArgs&&... __bargs)
+ : __f_(std::forward<_Gp>(__g), std::forward<_BArgs>(__bargs)...) {}
_LIBCPP_HIDE_FROM_ABI __async_func(__async_func&& __f) : __f_(std::move(__f.__f_)) {}
@@ -1861,8 +1862,7 @@ async(launch __policy, _Fp&& __f, _Args&&... __args) {
try {
# endif
if (__does_policy_contain(__policy, launch::async))
- return std::__make_async_assoc_state<_Rp>(
- _BF(_LIBCPP_AUTO_CAST(std::forward<_Fp>(__f)), _LIBCPP_AUTO_CAST(std::forward<_Args>(__args))...));
+ return std::__make_async_assoc_state<_Rp>(_BF(std::forward<_Fp>(__f), std::forward<_Args>(__args)...));
# if _LIBCPP_HAS_EXCEPTIONS
} catch (...) {
if (__policy == launch::async)
@@ -1871,8 +1871,7 @@ async(launch __policy, _Fp&& __f, _Args&&... __args) {
# endif
if (__does_policy_contain(__policy, launch::deferred))
- return std::__make_deferred_assoc_state<_Rp>(
- _BF(_LIBCPP_AUTO_CAST(std::forward<_Fp>(__f)), _LIBCPP_AUTO_CAST(std::forward<_Args>(__args))...));
+ return std::__make_deferred_assoc_state<_Rp>(_BF(std::forward<_Fp>(__f), std::forward<_Args>(__args)...));
return future<_Rp>{};
}
diff --git a/libcxx/test/std/thread/futures/futures.async/async.pass.cpp b/libcxx/test/std/thread/futures/futures.async/async.pass.cpp
index 109372b50a311..81232492c2545 100644
--- a/libcxx/test/std/thread/futures/futures.async/async.pass.cpp
+++ b/libcxx/test/std/thread/futures/futures.async/async.pass.cpp
@@ -155,5 +155,30 @@ int main(int, char**)
try { f.get(); assert (false); } catch ( int ) {}
}
#endif
+ {
+ class CopyOnly {
+ public:
+ CopyOnly() {}
+ CopyOnly(const CopyOnly&) = default;
+ CopyOnly(CopyOnly&&) = delete;
+
+ void operator()(const CopyOnly&) const {}
+ };
+ CopyOnly c;
+ std::future<void> f = std::async(c, c);
+ f.wait();
+ }
+ {
+ class MoveOnly {
+ public:
+ MoveOnly() {}
+ MoveOnly(const MoveOnly&) = delete;
+ MoveOnly(MoveOnly&&) = default;
+
+ void operator()(MoveOnly&&) const {}
+ };
+ std::future<void> f = std::async(MoveOnly{}, MoveOnly{});
+ f.wait();
+ }
return 0;
}
diff --git a/libcxx/test/std/thread/futures/futures.async/async.verify.cpp b/libcxx/test/std/thread/futures/futures.async/async.verify.cpp
index 6b686cd9a1747..3c39b1264f1fe 100644
--- a/libcxx/test/std/thread/futures/futures.async/async.verify.cpp
+++ b/libcxx/test/std/thread/futures/futures.async/async.verify.cpp
@@ -28,6 +28,22 @@ void f() {
std::async( foo, 3); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::async(std::launch::async, foo, 3); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ { // Mandates: is_constructible_v<decay_t<F>, F>
+ struct F {
+ F() = default;
+ F(const F&) = delete;
+ F(F&&) = default;
+
+ void operator()() {}
+ };
+
+ F f;
+ static_cast<void>(std::async(f));
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ // expected-error@*:* {{call to deleted constructor}}
+ }
+
{ // Mandates: is_constructible_v<decay_t<F>, F>
struct F {
F() = default;
@@ -39,7 +55,47 @@ void f() {
static_cast<void>(std::async(F{}));
// expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{no matching constructor for initialization}}
// expected-error@*:* {{call to deleted constructor}}
- // expected-error@*:* 1+ {{uses deleted function}}
+ }
+
+ { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+ struct Arg {
+ Arg() = default;
+ Arg(const Arg&) = delete;
+ Arg(Arg&&) = default;
+ };
+
+ struct F {
+ void operator()(const Arg&) const {}
+ };
+
+ Arg arg;
+ static_cast<void>(std::async(F{}, arg));
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{call to deleted constructor}}
+ }
+
+ { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+ struct Arg {
+ Arg() = default;
+ Arg(const Arg&) = default;
+ Arg(Arg&&) = delete;
+ };
+
+ struct F {
+ void operator()(const Arg&) const {}
+ };
+
+ static_cast<void>(std::async(F{}, Arg{}));
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{call to deleted constructor}}
+ }
+
+ { // Mandates: is_invocable_v<decay_t<F>, decay_t<Args>...>
+ struct F {};
+
+ static_cast<void>(std::async(F{}));
+ // expected-error@*:* {{no matching function}}
}
}
diff --git a/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp
index e8a57cc684138..9342e94560491 100644
--- a/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp
+++ b/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp
@@ -155,5 +155,30 @@ int main(int, char**) {
//
// Unfortunately, this is extremely hard to test portably so we don't have a test for this error condition right now.
+ {
+ class CopyOnly {
+ public:
+ CopyOnly() {}
+ CopyOnly(const CopyOnly&) = default;
+ CopyOnly(CopyOnly&&) = delete;
+
+ void operator()(const CopyOnly&) const {}
+ };
+ CopyOnly c;
+ std::jthread t(c, c);
+ }
+
+ {
+ class MoveOnly {
+ public:
+ MoveOnly() {}
+ MoveOnly(const MoveOnly&) = delete;
+ MoveOnly(MoveOnly&&) = default;
+
+ void operator()(MoveOnly&&) const {}
+ };
+ std::jthread t(MoveOnly{}, MoveOnly{});
+ }
+
return 0;
}
diff --git a/libcxx/test/std/thread/thread.jthread/cons.verify.cpp b/libcxx/test/std/thread/thread.jthread/cons.verify.cpp
new file mode 100644
index 0000000000000..fe96b07e0bedc
--- /dev/null
+++ b/libcxx/test/std/thread/thread.jthread/cons.verify.cpp
@@ -0,0 +1,95 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <jthread>
+
+// class jthread
+
+// template<class F, class... Args>
+// explicit jthread(F&& f, Args&&... args);
+
+#include <thread>
+
+void test() {
+ { // Mandates: is_constructible_v<decay_t<F>, F>
+ struct F {
+ F() = default;
+ F(const F&) = delete;
+ F(F&&) = default;
+
+ void operator()() {}
+ };
+
+ F f;
+ std::jthread t(f);
+ // expected-error@*:* 2 {{static assertion failed}}
+ // expected-error@*:* 2 {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: is_constructible_v<decay_t<F>, F>
+ struct F {
+ F() = default;
+ F(const F&) = default;
+ F(F&&) = delete;
+
+ void operator()() {}
+ };
+
+ std::jthread t(F{});
+ // expected-error@*:* 2 {{static assertion failed}}
+ // expected-error@*:* 2 {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+ struct Arg {
+ Arg() = default;
+ Arg(const Arg&) = delete;
+ Arg(Arg&&) = default;
+ };
+
+ struct F {
+ void operator()(const Arg&) const {}
+ };
+
+ Arg arg;
+ std::jthread t(F{}, arg);
+ // expected-error@*:* 2 {{static assertion failed}}
+ // expected-error@*:* 2 {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+ struct Arg {
+ Arg() = default;
+ Arg(const Arg&) = default;
+ Arg(Arg&&) = delete;
+ };
+
+ struct F {
+ void operator()(const Arg&) const {}
+ };
+
+ std::jthread t(F{}, Arg{});
+ // expected-error@*:* 2 {{static assertion failed}}
+ // expected-error@*:* 2 {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: is_invocable_v<decay_t<F>, decay_t<Args>...>
+ struct F {};
+
+ std::jthread t(F{});
+ // expected-error@*:* 2 {{static assertion failed}}
+ // expected-error@*:* {{no matching function for call to '__invoke'}}
+ }
+}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
index 363f7c14559a8..6f7e5291b576e 100644
--- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.pass.cpp
@@ -106,8 +106,8 @@ class MoveOnly
class CopyOnly {
public:
CopyOnly() {}
- CopyOnly(const CopyOnly&) {}
- CopyOnly(CopyOnly&&) = delete;
+ CopyOnly(const CopyOnly&) = default;
+ CopyOnly(CopyOnly&&) = delete;
void operator()(const CopyOnly&) const {}
};
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
index 1b8b8138f8066..4055005ad6ca9 100644
--- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
@@ -23,7 +23,7 @@ void test() {
struct F {
F() = default;
F(const F&) = delete;
- F(F&&) = delete;
+ F(F&&) = default;
void operator()() {}
};
@@ -35,11 +35,26 @@ void test() {
// expected-error@*:* {{no matching constructor for initialization}}
}
+ { // Mandates: is_constructible_v<decay_t<F>, F>
+ struct F {
+ F() = default;
+ F(const F&) = default;
+ F(F&&) = delete;
+
+ void operator()() {}
+ };
+
+ std::thread t(F{});
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
{ // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
struct Arg {
Arg() = default;
Arg(const Arg&) = delete;
- Arg(Arg&&) = delete;
+ Arg(Arg&&) = default;
};
struct F {
@@ -49,8 +64,25 @@ void test() {
Arg arg;
std::thread t(F{}, arg);
// expected-error@*:* {{static assertion failed}}
- // expected-error@*:* 0+ {{call to deleted constructor}}
- // expected-error@*:* 0+ {{no matching constructor for initialization}}
+ // expected-error@*:* {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
+ }
+
+ { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+ struct Arg {
+ Arg() = default;
+ Arg(const Arg&) = default;
+ Arg(Arg&&) = delete;
+ };
+
+ struct F {
+ void operator()(const Arg&) const {}
+ };
+
+ std::thread t(F{}, Arg{});
+ // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{call to deleted constructor}}
+ // expected-error@*:* {{no matching constructor for initialization}}
}
{ // Mandates: is_invocable_v<decay_t<F>, decay_t<Args>...>
>From d29242a8ce18e8605d2452036b31d8d8d59f723f Mon Sep 17 00:00:00 2001
From: mleleszi <mleleszi at icloud.com>
Date: Tue, 23 Dec 2025 13:46:05 +0100
Subject: [PATCH 3/3] Address comments
---
libcxx/docs/Status/Cxx23Issues.csv | 2 +-
libcxx/include/__thread/jthread.h | 12 ++++++------
libcxx/include/__thread/thread.h | 8 +++-----
.../test/std/thread/thread.jthread/cons.verify.cpp | 4 ++--
.../thread.thread.constr/F.verify.cpp | 4 +---
5 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv
index ef48759172382..cc83a7da9c25e 100644
--- a/libcxx/docs/Status/Cxx23Issues.csv
+++ b/libcxx/docs/Status/Cxx23Issues.csv
@@ -45,7 +45,7 @@
"`LWG3472 <https://wg21.link/LWG3472>`__","``counted_iterator`` is missing preconditions","2020-11 (Virtual)","|Complete|","14","`#104304 <https://github.com/llvm/llvm-project/issues/104304>`__",""
"`LWG3473 <https://wg21.link/LWG3473>`__","Normative encouragement in non-normative note","2020-11 (Virtual)","|Complete|","15","`#104305 <https://github.com/llvm/llvm-project/issues/104305>`__",""
"`LWG3474 <https://wg21.link/LWG3474>`__","Nesting ``join_views`` is broken because of CTAD","2020-11 (Virtual)","|Complete|","15","`#104306 <https://github.com/llvm/llvm-project/issues/104306>`__",""
-"`LWG3476 <https://wg21.link/LWG3476>`__","``thread`` and ``jthread`` constructors require that the parameters be move-constructible but never move construct the parameters","2020-11 (Virtual)","|Complete|","","`#104307 <https://github.com/llvm/llvm-project/issues/104307>`__",""
+"`LWG3476 <https://wg21.link/LWG3476>`__","``thread`` and ``jthread`` constructors require that the parameters be move-constructible but never move construct the parameters","2020-11 (Virtual)","|Complete|","22","`#104307 <https://github.com/llvm/llvm-project/issues/104307>`__",""
"`LWG3477 <https://wg21.link/LWG3477>`__","Simplify constraints for ``semiregular-box``","2020-11 (Virtual)","|Complete|","13","`#104308 <https://github.com/llvm/llvm-project/issues/104308>`__",""
"`LWG3482 <https://wg21.link/LWG3482>`__","``drop_view``'s const begin should additionally require ``sized_range``","2020-11 (Virtual)","|Complete|","14","`#104309 <https://github.com/llvm/llvm-project/issues/104309>`__",""
"`LWG3483 <https://wg21.link/LWG3483>`__","``transform_view::iterator``'s difference is overconstrained","2020-11 (Virtual)","|Complete|","14","`#104310 <https://github.com/llvm/llvm-project/issues/104310>`__",""
diff --git a/libcxx/include/__thread/jthread.h b/libcxx/include/__thread/jthread.h
index 632e33bcfd447..481ffe296c271 100644
--- a/libcxx/include/__thread/jthread.h
+++ b/libcxx/include/__thread/jthread.h
@@ -49,7 +49,12 @@ class jthread {
_LIBCPP_HIDE_FROM_ABI explicit jthread(_Fun&& __fun, _Args&&... __args)
requires(!std::is_same_v<remove_cvref_t<_Fun>, jthread>)
: __stop_source_(),
- __thread_(__init_thread(__stop_source_, std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) {}
+ __thread_(__init_thread(__stop_source_, std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) {
+ static_assert(is_constructible_v<decay_t<_Fun>, _Fun>);
+ static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...));
+ static_assert(is_invocable_v<decay_t<_Fun>, decay_t<_Args>...> ||
+ is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>);
+ }
_LIBCPP_HIDE_FROM_ABI ~jthread() {
if (joinable()) {
@@ -111,11 +116,6 @@ class jthread {
private:
template <class _Fun, class... _Args>
_LIBCPP_HIDE_FROM_ABI static thread __init_thread(const stop_source& __ss, _Fun&& __fun, _Args&&... __args) {
- static_assert(is_constructible_v<decay_t<_Fun>, _Fun>);
- static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...));
- static_assert(is_invocable_v<decay_t<_Fun>, decay_t<_Args>...> ||
- is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>);
-
if constexpr (is_invocable_v<decay_t<_Fun>, stop_token, decay_t<_Args>...>) {
return thread(std::forward<_Fun>(__fun), __ss.get_token(), std::forward<_Args>(__args)...);
} else {
diff --git a/libcxx/include/__thread/thread.h b/libcxx/include/__thread/thread.h
index 24b4caa7f0d2f..b2f51aa816c10 100644
--- a/libcxx/include/__thread/thread.h
+++ b/libcxx/include/__thread/thread.h
@@ -207,11 +207,9 @@ class _LIBCPP_EXPORTED_FROM_ABI thread {
# ifndef _LIBCPP_CXX03_LANG
template <class _Fp, class... _Args, __enable_if_t<!is_same<__remove_cvref_t<_Fp>, thread>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI explicit thread(_Fp&& __f, _Args&&... __args) {
-# if _LIBCPP_STD_VER >= 17
- static_assert(is_constructible_v<__decay_t<_Fp>, _Fp>);
- static_assert((is_constructible_v<__decay_t<_Args>, _Args> && ...));
- static_assert(is_invocable_v<__decay_t<_Fp>, __decay_t<_Args>...>);
-# endif
+ static_assert(is_constructible<__decay_t<_Fp>, _Fp>::value, "");
+ static_assert(_And<is_constructible<__decay_t<_Args>, _Args>...>::value, "");
+ static_assert(__is_invocable_v<__decay_t<_Fp>, __decay_t<_Args>...>, "");
typedef unique_ptr<__thread_struct> _TSPtr;
_TSPtr __tsp(new __thread_struct);
diff --git a/libcxx/test/std/thread/thread.jthread/cons.verify.cpp b/libcxx/test/std/thread/thread.jthread/cons.verify.cpp
index fe96b07e0bedc..5c8fb22ec4e67 100644
--- a/libcxx/test/std/thread/thread.jthread/cons.verify.cpp
+++ b/libcxx/test/std/thread/thread.jthread/cons.verify.cpp
@@ -64,7 +64,7 @@ void test() {
Arg arg;
std::jthread t(F{}, arg);
// expected-error@*:* 2 {{static assertion failed}}
- // expected-error@*:* 2 {{call to deleted constructor}}
+ // expected-error@*:* {{call to deleted constructor}}
// expected-error@*:* {{no matching constructor for initialization}}
}
@@ -81,7 +81,7 @@ void test() {
std::jthread t(F{}, Arg{});
// expected-error@*:* 2 {{static assertion failed}}
- // expected-error@*:* 2 {{call to deleted constructor}}
+ // expected-error@*:* {{call to deleted constructor}}
// expected-error@*:* {{no matching constructor for initialization}}
}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
index 4055005ad6ca9..8970f8c024290 100644
--- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.constr/F.verify.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
// UNSUPPORTED: no-threads
-// UNSUPPORTED: c++03, c++11, c++14
+// UNSUPPORTED: c++03
// <thread>
@@ -64,7 +64,6 @@ void test() {
Arg arg;
std::thread t(F{}, arg);
// expected-error@*:* {{static assertion failed}}
- // expected-error@*:* {{call to deleted constructor}}
// expected-error@*:* {{no matching constructor for initialization}}
}
@@ -81,7 +80,6 @@ void test() {
std::thread t(F{}, Arg{});
// expected-error@*:* {{static assertion failed}}
- // expected-error@*:* {{call to deleted constructor}}
// expected-error@*:* {{no matching constructor for initialization}}
}
More information about the libcxx-commits
mailing list