diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -677,6 +677,7 @@ __system_error/system_error.h __thread/formatter.h __thread/id.h + __thread/jthread.h __thread/poll_with_backoff.h __thread/this_thread.h __thread/thread.h diff --git a/libcxx/include/__stop_token/stop_state.h b/libcxx/include/__stop_token/stop_state.h --- a/libcxx/include/__stop_token/stop_state.h +++ b/libcxx/include/__stop_token/stop_state.h @@ -14,9 +14,10 @@ #include <__config> #include <__stop_token/atomic_unique_lock.h> #include <__stop_token/intrusive_list_view.h> +#include <__thread/this_thread.h> +#include <__thread/thread.h> #include #include -#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header diff --git a/libcxx/include/__thread/jthread.h b/libcxx/include/__thread/jthread.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__thread/jthread.h @@ -0,0 +1,133 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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___THREAD_JTHREAD_H +#define _LIBCPP___THREAD_JTHREAD_H + +#include <__availability> +#include <__config> +#include <__functional/invoke.h> +#include <__stop_token/stop_source.h> +#include <__stop_token/stop_token.h> +#include <__thread/thread.h> +#include <__threading_support> +#include <__type_traits/decay.h> +#include <__type_traits/is_constructible.h> +#include <__type_traits/is_same.h> +#include <__type_traits/remove_cvref.h> +#include <__utility/forward.h> +#include <__utility/move.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +#ifdef _LIBCPP_HAS_NO_THREADS +# error " is not supported since libc++ has been configured without support for threads." +#endif + +#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + +_LIBCPP_BEGIN_NAMESPACE_STD + +class _LIBCPP_AVAILABILITY_SYNC jthread { +public: + // types + using id = thread::id; + using native_handle_type = thread::native_handle_type; + + // [thread.jthread.cons], constructors, move, and assignment + _LIBCPP_HIDE_FROM_ABI jthread() noexcept : __stop_source_(std::nostopstate) {} + + template + _LIBCPP_HIDE_FROM_ABI explicit jthread(_Fun&& __fun, _Args&&... __args) + requires(!std::is_same_v, jthread>) + : __stop_source_(), __thread_(__init_thread(std::forward<_Fun>(__fun), std::forward<_Args>(__args)...)) { + static_assert(is_constructible_v, _Fun>); + static_assert((is_constructible_v, _Args> && ...)); + static_assert(is_invocable_v, decay_t<_Args>...> || + is_invocable_v, stop_token, decay_t<_Args>...>); + } + + _LIBCPP_HIDE_FROM_ABI ~jthread() { + if (joinable()) { + request_stop(); + join(); + } + } + + jthread(const jthread&) = delete; + + _LIBCPP_HIDE_FROM_ABI jthread(jthread&&) noexcept = default; + + jthread& operator=(const jthread&) = delete; + + _LIBCPP_HIDE_FROM_ABI jthread& operator=(jthread&& __other) noexcept { + if (this != &__other) { + if (joinable()) { + request_stop(); + join(); + } + __stop_source_ = std::move(__other.__stop_source_); + __thread_ = std::move(__other.__thread_); + } + + return *this; + } + + // [thread.jthread.mem], members + _LIBCPP_HIDE_FROM_ABI void swap(jthread& __other) noexcept { + std::swap(__stop_source_, __other.__stop_source_); + std::swap(__thread_, __other.__thread_); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool joinable() const noexcept { return get_id() != id(); } + + _LIBCPP_HIDE_FROM_ABI void join() { __thread_.join(); } + + _LIBCPP_HIDE_FROM_ABI void detach() { __thread_.detach(); } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI id get_id() const noexcept { return __thread_.get_id(); } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI native_handle_type native_handle() { return __thread_.native_handle(); } + + // [thread.jthread.stop], stop token handling + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI stop_source get_stop_source() noexcept { return __stop_source_; } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI stop_token get_stop_token() const noexcept { return __stop_source_.get_token(); } + + _LIBCPP_HIDE_FROM_ABI bool request_stop() noexcept { return __stop_source_.request_stop(); } + + // [thread.jthread.special], specialized algorithms + _LIBCPP_HIDE_FROM_ABI friend void swap(jthread& __lhs, jthread& __rhs) noexcept { __lhs.swap(__rhs); } + + // [thread.jthread.static], static members + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static unsigned int hardware_concurrency() noexcept { + return thread::hardware_concurrency(); + } + +private: + template + _LIBCPP_HIDE_FROM_ABI thread __init_thread(_Fun&& __fun, _Args&&... __args) { + if constexpr (is_invocable_v, stop_token, decay_t<_Args>...>) { + return thread(std::forward<_Fun>(__fun), __stop_source_.get_token(), std::forward<_Args>(__args)...); + } else { + return thread(std::forward<_Fun>(__fun), std::forward<_Args>(__args)...); + } + } + + stop_source __stop_source_; + thread __thread_; +}; + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + +#endif // _LIBCPP___THREAD_JTHREAD_H diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1789,6 +1789,7 @@ module std_private_thread_formatter [system] { header "__thread/formatter.h" } module std_private_thread_id [system] { header "__thread/id.h" } +module std_private_thread_jthread [system] { header "__thread/jthread.h" } module std_private_thread_poll_with_backoff [system] { header "__thread/poll_with_backoff.h" } module std_private_thread_this_thread [system] { header "__thread/this_thread.h" } module std_private_thread_thread [system] { diff --git a/libcxx/include/thread b/libcxx/include/thread --- a/libcxx/include/thread +++ b/libcxx/include/thread @@ -90,6 +90,7 @@ #include <__availability> #include <__config> #include <__thread/formatter.h> +#include <__thread/jthread.h> #include <__thread/this_thread.h> #include <__thread/thread.h> #include <__threading_support> diff --git a/libcxx/modules/std/thread.inc b/libcxx/modules/std/thread.inc --- a/libcxx/modules/std/thread.inc +++ b/libcxx/modules/std/thread.inc @@ -15,7 +15,9 @@ using std::swap; // [thread.jthread.class], class jthread - // using std::jthread; +# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + using std::jthread; +# endif // [thread.thread.this], namespace this_thread namespace this_thread { diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -119,11 +119,9 @@ chrono cstdint chrono cstring chrono ctime -chrono forward_list chrono limits chrono ratio chrono stdexcept -chrono string chrono string_view chrono tuple chrono type_traits @@ -801,13 +799,19 @@ stdexcept exception stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token type_traits stop_token version streambuf cstdint @@ -867,6 +871,7 @@ system_error type_traits system_error version thread array +thread atomic thread cerrno thread chrono thread compare diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -119,11 +119,9 @@ chrono cstdint chrono cstring chrono ctime -chrono forward_list chrono limits chrono ratio chrono stdexcept -chrono string chrono string_view chrono tuple chrono type_traits @@ -807,13 +805,19 @@ stdexcept exception stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token type_traits stop_token version streambuf cstdint @@ -873,6 +877,7 @@ system_error type_traits system_error version thread array +thread atomic thread cerrno thread chrono thread compare diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -119,11 +119,9 @@ chrono cstdint chrono cstring chrono ctime -chrono forward_list chrono limits chrono ratio chrono stdexcept -chrono string chrono string_view chrono tuple chrono type_traits @@ -809,13 +807,19 @@ stdexcept exception stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token type_traits stop_token version streambuf cstdint @@ -875,6 +879,7 @@ system_error type_traits system_error version thread array +thread atomic thread cerrno thread chrono thread compare diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -119,11 +119,9 @@ chrono cstdint chrono cstring chrono ctime -chrono forward_list chrono limits chrono ratio chrono stdexcept -chrono string chrono string_view chrono tuple chrono type_traits @@ -809,13 +807,19 @@ stdexcept exception stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token type_traits stop_token version streambuf cstdint @@ -875,6 +879,7 @@ system_error type_traits system_error version thread array +thread atomic thread cerrno thread chrono thread compare diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -121,7 +121,6 @@ chrono cstdint chrono cstring chrono ctime -chrono forward_list chrono limits chrono locale chrono optional @@ -814,13 +813,19 @@ stdexcept exception stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token type_traits stop_token version streambuf cstdint @@ -880,6 +885,7 @@ system_error type_traits system_error version thread array +thread atomic thread cerrno thread compare thread cstddef diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -73,7 +73,6 @@ chrono cstddef chrono cstdint chrono ctime -chrono forward_list chrono initializer_list chrono limits chrono locale @@ -580,13 +579,19 @@ stack version stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token version streambuf cstdint streambuf ios @@ -629,6 +634,7 @@ system_error string system_error version thread array +thread atomic thread cerrno thread compare thread cstddef diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -73,7 +73,6 @@ chrono cstddef chrono cstdint chrono ctime -chrono forward_list chrono initializer_list chrono limits chrono locale @@ -580,13 +579,19 @@ stack version stdexcept iosfwd stop_token atomic +stop_token cerrno stop_token cstddef stop_token cstdint stop_token cstring stop_token ctime +stop_token iosfwd stop_token limits +stop_token locale stop_token ratio -stop_token thread +stop_token sstream +stop_token stdexcept +stop_token string +stop_token tuple stop_token version streambuf cstdint streambuf ios @@ -629,6 +634,7 @@ system_error string system_error version thread array +thread atomic thread cerrno thread compare thread cstddef diff --git a/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp b/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp @@ -0,0 +1,115 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing +// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move + +// jthread& operator=(jthread&&) noexcept; + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_assignable_v); + +int main(int, char**) { + // If &x == this is true, there are no effects. + { + std::jthread j([] {}); + auto id = j.get_id(); + auto ssource = j.get_stop_source(); + j = std::move(j); + assert(j.get_id() == id); + assert(j.get_stop_source() == ssource); + } + + // if joinable() is true, calls request_stop() and then join() + // request_stop is called + { + std::jthread j1([] {}); + bool called = false; + std::stop_callback cb(j1.get_stop_token(), [&called] { called = true; }); + + std::jthread j2([] {}); + j1 = std::move(j2); + assert(called); + } + + // if joinable() is true, calls request_stop() and then join() + // join is called + { + std::atomic_int calledTimes = 0; + std::vector jts; + constexpr auto numberOfThreads = 10u; + jts.reserve(numberOfThreads); + for (auto i = 0u; i < numberOfThreads; ++i) { + jts.emplace_back([&] { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + calledTimes.fetch_add(1, std::memory_order_relaxed); + }); + } + + for (auto i = 0u; i < numberOfThreads; ++i) { + jts[i] = std::jthread{}; + } + + // If join was called as expected, calledTimes must equal to numberOfThreads + // If join was not called, there is a chance that the check below happened + // before test threads incrementing the counter, thus calledTimed would + // be less than numberOfThreads. + // This is not going to catch issues 100%. Creating more threads to increase + // the probability of catching the issue + assert(calledTimes.load(std::memory_order_relaxed) == numberOfThreads); + } + + // then assigns the state of x to *this + { + std::jthread j1([] {}); + std::jthread j2([] {}); + auto id2 = j2.get_id(); + auto ssource2 = j2.get_stop_source(); + + j1 = std::move(j2); + + assert(j1.get_id() == id2); + assert(j1.get_stop_source() == ssource2); + } + + // sets x to a default constructed state + { + std::jthread j1([] {}); + std::jthread j2([] {}); + j1 = std::move(j2); + + assert(j2.get_id() == std::jthread::id()); + assert(!j2.get_stop_source().stop_possible()); + } + + // joinable is false + { + std::jthread j1; + std::jthread j2([] {}); + + auto j2Id = j2.get_id(); + + j1 = std::move(j2); + + assert(j1.get_id() == j2Id); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/cons.default.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// jthread() noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_default_constructible_v); + +int main(int, char**) { + { + std::jthread jt = {}; // implicit + assert(!jt.get_stop_source().stop_possible()); + assert(jt.get_id() == std::jthread::id()); + } + + return 0; +} 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 new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/cons.func.token.pass.cpp @@ -0,0 +1,157 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// template +// explicit jthread(F&& f, Args&&... args); + +#include +#include +#include +#include + +#include "test_macros.h" + +template +struct Func { + void operator()(Args...) const; +}; + +// Constraints: remove_cvref_t is not the same type as jthread. +static_assert(std::is_constructible_v>); +static_assert(std::is_constructible_v, int>); +static_assert(!std::is_constructible_v); + +// explicit +template +void conversion_test(T); + +template +concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test({std::forward(args)...}); }; + +static_assert(!ImplicitlyConstructible>); +static_assert(!ImplicitlyConstructible, int>); + +int main(int, char**) { + // Effects: Initializes ssource + // Postconditions: get_id() != id() is true and ssource.stop_possible() is true + // and *this represents the newly started thread. + { + std::jthread jt{[] {}}; + assert(jt.get_stop_source().stop_possible()); + assert(jt.get_id() != std::jthread::id()); + } + + // The new thread of execution executes + // invoke(auto(std::forward(f)), get_stop_token(), auto(std::forward(args))...) + // if that expression is well-formed, + { + int result = 0; + std::jthread jt{[&result](std::stop_token st, int i) { + assert(st.stop_possible()); + assert(!st.stop_requested()); + result += i; + }, + 5}; + jt.join(); + assert(result == 5); + } + + // otherwise + // invoke(auto(std::forward(f)), auto(std::forward(args))...) + { + int result = 0; + std::jthread jt{[&result](int i) { result += i; }, 5}; + jt.join(); + assert(result == 5); + } + + // with the values produced by auto being materialized ([conv.rval]) in the constructing thread. + { + struct TrackThread { + std::jthread::id threadId; + bool copyConstructed = false; + bool moveConstructed = false; + + TrackThread() : threadId(std::this_thread::get_id()) {} + TrackThread(const TrackThread&) : threadId(std::this_thread::get_id()), copyConstructed(true) {} + TrackThread(TrackThread&&) : threadId(std::this_thread::get_id()), moveConstructed(true) {} + }; + + auto mainThread = std::this_thread::get_id(); + + TrackThread arg1; + std::jthread jt1{[mainThread](const TrackThread& arg) { + assert(arg.threadId == mainThread); + assert(arg.threadId != std::this_thread::get_id()); + assert(arg.copyConstructed); + }, + arg1}; + + TrackThread arg2; + std::jthread jt2{[mainThread](const TrackThread& arg) { + assert(arg.threadId == mainThread); + assert(arg.threadId != std::this_thread::get_id()); + assert(arg.moveConstructed); + }, + std::move(arg2)}; + } + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + // [Note 1: This implies that any exceptions not thrown from the invocation of the copy + // of f will be thrown in the constructing thread, not the new thread. - end note] + { + struct Exception { + std::jthread::id threadId; + }; + struct ThrowOnCopyFunc { + ThrowOnCopyFunc() = default; + ThrowOnCopyFunc(const ThrowOnCopyFunc&) { throw Exception{std::this_thread::get_id()}; } + void operator()() const {} + }; + ThrowOnCopyFunc f1; + try { + std::jthread jt{f1}; + assert(false); + } catch (const Exception& e) { + assert(e.threadId == std::this_thread::get_id()); + } + } +#endif // !defined(TEST_HAS_NO_EXCEPTIONS) + + // Synchronization: The completion of the invocation of the constructor + // synchronizes with the beginning of the invocation of the copy of f. + { + int flag = 0; + struct Arg { + int& flag_; + Arg(int& f) : flag_(f) {} + + Arg(const Arg& other) : flag_(other.flag_) { flag_ = 5; } + }; + + Arg arg(flag); + std::jthread jt( + [&flag](const auto&) { + assert(flag == 5); // happens-after the copy-construction of arg + }, + arg); + } + + // TODO: test the following: + // + // Throws: system_error if unable to start the new thread. + // Error conditions: + // resource_unavailable_try_again - the system lacked the necessary resources to create another thread, + // or the system-imposed limit on the number of threads in a process would be exceeded. + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// jthread(jthread&& x) noexcept; + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +static_assert(std::is_nothrow_move_constructible_v); + +int main(int, char**) { + { + // x.get_id() == id() and get_id() returns the value of x.get_id() prior + // to the start of construction. + std::jthread j1{[] {}}; + auto id1 = j1.get_id(); + + std::jthread j2(std::move(j1)); + assert(j2.get_id() == id1); + } + + { + // ssource has the value of x.ssource prior to the start of construction + // and x.ssource.stop_possible() is false. + std::jthread j1{[] {}}; + auto ss1 = j1.get_stop_source(); + + std::jthread j2(std::move(j1)); + assert(ss1 == j2.get_stop_source()); + assert(!j1.get_stop_source().stop_possible()); + assert(j2.get_stop_source().stop_possible()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp b/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/copy.delete.compile.pass.cpp @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// jthread(const jthread&) = delete; +// jthread& operator=(const jthread&) = delete; + +#include +#include + +static_assert(!std::is_copy_constructible_v); +static_assert(!std::is_copy_assignable_v); diff --git a/libcxx/test/std/thread/thread.jthread/detach.pass.cpp b/libcxx/test/std/thread/thread.jthread/detach.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/detach.pass.cpp @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// void detach(); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +int main(int, char**) { + // Effects: The thread represented by *this continues execution without the calling thread blocking. + { + std::atomic_bool start{false}; + std::atomic_bool done{false}; + std::optional jt{[&start, &done] { + start.wait(false); + done = true; + }}; + + // If it blocks, it will deadlock here + jt->detach(); + + jt.reset(); + + // The other thread continues execution + start = true; + start.notify_all(); + while (!done) { + } + } + + // Postconditions: get_id() == id(). + { + std::jthread jt{[] {}}; + assert(jt.get_id() != std::jthread::id()); + jt.detach(); + assert(jt.get_id() == std::jthread::id()); + } + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + // Throws: system_error when an exception is required ([thread.req.exception]). + // invalid_argument - if the thread is not joinable. + { + std::jthread jt; + try { + jt.detach(); + assert(false); + } catch (const std::system_error& err) { + assert(err.code() == std::errc::invalid_argument); + } + } + +#endif + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp b/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// ~jthread(); + +#include +#include +#include +#include +#include +#include +#include +#include "test_macros.h" + +int main(int, char**) { + // !joinable() + { + std::jthread jt; + assert(!jt.joinable()); + } + + // If joinable() is true, calls request_stop() and then join(). + // request_stop is called + { + std::optional jt([] {}); + bool called = false; + std::stop_callback cb(jt->get_stop_token(), [&called] { called = true; }); + jt.reset(); + assert(called); + } + + // If joinable() is true, calls request_stop() and then join(). + // join is called + { + std::atomic_int calledTimes = 0; + std::vector jts; + + constexpr auto numberOfThreads = 10u; + jts.reserve(numberOfThreads); + for (auto i = 0u; i < numberOfThreads; ++i) { + jts.emplace_back([&calledTimes] { + std::this_thread::sleep_for(std::chrono::milliseconds{2}); + calledTimes.fetch_add(1, std::memory_order_relaxed); + }); + } + jts.clear(); + + // If join was called as expected, calledTimes must equal to numberOfThreads + // If join was not called, there is a chance that the check below happened + // before test threads incrementing the counter, thus calledTimed would + // be less than numberOfThreads. + // This is not going to catch issues 100%. Creating more threads to increase + // the probability of catching the issue + assert(calledTimes.load(std::memory_order_relaxed) == numberOfThreads); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] id get_id() const noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsGetIdNoexcept = requires(const T& a) { + { a.get_id() } noexcept; +}; + +static_assert(IsGetIdNoexcept); + +int main(int, char**) { + // Does not represent a thread + { + const std::jthread jt; + std::same_as auto result = jt.get_id(); + assert(result == std::jthread::id()); + } + + // Represents a thread + { + const std::jthread jt{[] {}}; + std::same_as auto result = jt.get_id(); + assert(result != std::jthread::id()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] stop_source get_stop_source() noexcept; + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsGetStopSourceNoexcept = requires(T& a) { + { a.get_stop_source() } noexcept; +}; + +static_assert(IsGetStopSourceNoexcept); + +int main(int, char**) { + // Represents a thread + { + std::jthread jt{[] {}}; + std::same_as auto result = jt.get_stop_source(); + assert(result.stop_possible()); + } + + // Does not represents a thread + { + std::jthread jt{}; + std::same_as auto result = jt.get_stop_source(); + assert(!result.stop_possible()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] stop_token get_stop_token() const noexcept; + +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsGetStopTokenNoexcept = requires(const T& a) { + { a.get_stop_token() } noexcept; +}; + +static_assert(IsGetStopTokenNoexcept); + +int main(int, char**) { + // Represents a thread + { + std::jthread jt{[] {}}; + auto ss = jt.get_stop_source(); + std::same_as auto st = std::as_const(jt).get_stop_token(); + + assert(st.stop_possible()); + assert(!st.stop_requested()); + ss.request_stop(); + assert(st.stop_requested()); + } + + // Does not represent a thread + { + const std::jthread jt{}; + std::same_as auto st = jt.get_stop_token(); + + assert(!st.stop_possible()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp b/libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/hardware_concurrency.pass.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] static unsigned int hardware_concurrency() noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsHardwareConcurrencyNoexcept = requires { + { T::hardware_concurrency() } noexcept; +}; + +static_assert(IsHardwareConcurrencyNoexcept); + +int main(int, char**) { + std::same_as auto result = std::jthread::hardware_concurrency(); + assert(result == std::thread::hardware_concurrency()); + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/join.pass.cpp b/libcxx/test/std/thread/thread.jthread/join.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/join.pass.cpp @@ -0,0 +1,112 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// void join(); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +int main(int, char**) { + // Effects: Blocks until the thread represented by *this has completed. + { + std::atomic_int calledTimes = 0; + std::vector jts; + constexpr auto numberOfThreads = 10u; + jts.reserve(numberOfThreads); + for (auto i = 0u; i < numberOfThreads; ++i) { + jts.emplace_back([&] { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + calledTimes.fetch_add(1, std::memory_order_relaxed); + }); + } + + for (auto i = 0u; i < numberOfThreads; ++i) { + jts[i].join(); + } + + // If join did block, calledTimes must equal to numberOfThreads + // If join did not block, there is a chance that the check below happened + // before test threads incrementing the counter, thus calledTimed would + // be less than numberOfThreads. + // This is not going to catch issues 100%. Creating more threads to increase + // the probability of catching the issue + assert(calledTimes.load(std::memory_order_relaxed) == numberOfThreads); + } + + // Synchronization: The completion of the thread represented by *this synchronizes with + // ([intro.multithread]) the corresponding successful join() return. + { + bool flag = false; + std::jthread jt{[&] { flag = true; }}; + jt.join(); + assert(flag); // non atomic write is visible to the current thread + } + + // Postconditions: The thread represented by *this has completed. get_id() == id(). + { + std::jthread jt{[] {}}; + assert(jt.get_id() != std::jthread::id()); + jt.join(); + assert(jt.get_id() == std::jthread::id()); + } + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + // Throws: system_error when an exception is required ([thread.req.exception]). + // invalid_argument - if the thread is not joinable. + { + std::jthread jt; + try { + jt.join(); + assert(false); + } catch (const std::system_error& err) { + assert(err.code() == std::errc::invalid_argument); + } + } + + // resource_deadlock_would_occur - if deadlock is detected or get_id() == this_thread::get_id(). + { + std::function f; + std::atomic_bool start = false; + std::atomic_bool done = false; + + std::jthread jt{[&] { + start.wait(false); + f(); + done = true; + done.notify_all(); + }}; + + f = [&] { + try { + jt.join(); + assert(false); + } catch (const std::system_error& err) { + assert(err.code() == std::errc::resource_deadlock_would_occur); + } + }; + start = true; + start.notify_all(); + done.wait(false); + } +#endif + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp b/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] bool joinable() const noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsJoinableNoexcept = requires(const T& a) { + { a.joinable() } noexcept; +}; + +static_assert(IsJoinableNoexcept); + +int main(int, char**) { + + // Default constructed + { + const std::jthread jt; + std::same_as auto result = jt.joinable(); + assert(!result); + } + + // Non-default constructed + { + const std::jthread jt{[]{}}; + std::same_as auto result = jt.joinable(); + assert(result); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp b/libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/nodiscard.verify.cpp @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] bool joinable() const noexcept; +// [[nodiscard]] id get_id() const noexcept; +// [[nodiscard]] native_handle_type native_handle(); +// [[nodiscard]] stop_source get_stop_source() noexcept; +// [[nodiscard]] stop_token get_stop_token() const noexcept; +// [[nodiscard]] static unsigned int hardware_concurrency() noexcept; + +#include + +void test() { + std::jthread jt; + jt.joinable(); // expected-warning {{ignoring return value of function}} + jt.get_id(); // expected-warning {{ignoring return value of function}} + jt.native_handle(); // expected-warning {{ignoring return value of function}} + jt.get_stop_source(); // expected-warning {{ignoring return value of function}} + jt.get_stop_token(); // expected-warning {{ignoring return value of function}} + jt.hardware_concurrency(); // expected-warning {{ignoring return value of function}} +} diff --git a/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp b/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// [[nodiscard]] bool request_stop() noexcept; + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept IsRequestStopNoexcept = requires(T& a) { + { a.request_stop() } noexcept; +}; + +static_assert(IsRequestStopNoexcept); + +int main(int, char**) { + // Represents a thread + { + std::jthread jt{[] {}}; + auto st = jt.get_stop_token(); + assert(!st.stop_requested()); + std::same_as auto result = jt.request_stop(); + assert(result); + assert(st.stop_requested()); + } + + // Does not represent a thread + { + std::jthread jt{}; + std::same_as auto result = jt.request_stop(); + assert(!result); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp b/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// friend void swap(jthread& x, jthread& y) noexcept; + +#include +#include +#include + +#include "test_macros.h" + +template +concept IsFreeSwapNoexcept = requires(T& a, T& b) { + { swap(a, b) } noexcept; +}; + +static_assert(IsFreeSwapNoexcept); + +int main(int, char**) { + // x is default constructed + { + std::jthread t1; + std::jthread t2{[] {}}; + const auto originalId2 = t2.get_id(); + swap(t1, t2); + + assert(t1.get_id() == originalId2); + assert(t2.get_id() == std::jthread::id()); + } + + // y is default constructed + { + std::jthread t1([] {}); + std::jthread t2{}; + const auto originalId1 = t1.get_id(); + swap(t1, t2); + + assert(t1.get_id() == std::jthread::id()); + assert(t2.get_id() == originalId1); + } + + // both not default constructed + { + std::jthread t1([] {}); + std::jthread t2{[] {}}; + const auto originalId1 = t1.get_id(); + const auto originalId2 = t2.get_id(); + swap(t1, t2); + + assert(t1.get_id() == originalId2); + assert(t2.get_id() == originalId1); + } + + // both default constructed + { + std::jthread t1; + std::jthread t2; + swap(t1, t2); + + assert(t1.get_id() == std::jthread::id()); + assert(t2.get_id() == std::jthread::id()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp b/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// void swap(jthread& x) noexcept; + +#include +#include +#include + +#include "test_macros.h" + +template +concept IsMemberSwapNoexcept = requires(T& a, T& b) { + { a.swap(b) } noexcept; +}; + +static_assert(IsMemberSwapNoexcept); + +int main(int, char**) { + // this is default constructed + { + std::jthread t1; + std::jthread t2{[] {}}; + const auto originalId2 = t2.get_id(); + t1.swap(t2); + + assert(t1.get_id() == originalId2); + assert(t2.get_id() == std::jthread::id()); + } + + // that is default constructed + { + std::jthread t1([] {}); + std::jthread t2{}; + const auto originalId1 = t1.get_id(); + t1.swap(t2); + + assert(t1.get_id() == std::jthread::id()); + assert(t2.get_id() == originalId1); + } + + // both not default constructed + { + std::jthread t1([] {}); + std::jthread t2{[] {}}; + const auto originalId1 = t1.get_id(); + const auto originalId2 = t2.get_id(); + t1.swap(t2); + + assert(t1.get_id() == originalId2); + assert(t2.get_id() == originalId1); + } + + // both default constructed + { + std::jthread t1; + std::jthread t2; + t1.swap(t2); + + assert(t1.get_id() == std::jthread::id()); + assert(t2.get_id() == std::jthread::id()); + } + + return 0; +} diff --git a/libcxx/test/std/thread/thread.jthread/typedef.compile.pass.cpp b/libcxx/test/std/thread/thread.jthread/typedef.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.jthread/typedef.compile.pass.cpp @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// XFAIL: availability-synchronization_library-missing + +// using id = thread::id; +// using native_handle_type = thread::native_handle_type; + +#include +#include + +static_assert(std::is_same_v); +static_assert(std::is_same_v);