Fixes https://github.com/llvm/llvm-project/issues/104307 This patch implements LWG3476 by removing the incorrect decay-copy in std::async. The decay-copy was being applied twice, once explicitly via _LIBCPP_AUTO_CAST and once in __async_func's tuple constructor. (https://github.com/llvm/llvm-project/issues/143828) It also adds static_assert mandates to std::thread and std::async (which were already implicitly enforced) and expands test coverage.
185 lines
5.6 KiB
C++
185 lines
5.6 KiB
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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// UNSUPPORTED: no-threads
|
|
// UNSUPPORTED: c++03, c++11, c++14, c++17
|
|
|
|
// template<class F, class... Args>
|
|
// explicit jthread(F&& f, Args&&... args);
|
|
|
|
#include <cassert>
|
|
#include <stop_token>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "test_macros.h"
|
|
|
|
template <class... Args>
|
|
struct Func {
|
|
void operator()(Args...) const;
|
|
};
|
|
|
|
// Constraints: remove_cvref_t<F> is not the same type as jthread.
|
|
static_assert(std::is_constructible_v<std::jthread, Func<>>);
|
|
static_assert(std::is_constructible_v<std::jthread, Func<int>, int>);
|
|
static_assert(!std::is_constructible_v<std::jthread, std::jthread const&>);
|
|
|
|
// explicit
|
|
template <class T>
|
|
void conversion_test(T);
|
|
|
|
template <class T, class... Args>
|
|
concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test<T>({std::forward<Args>(args)...}); };
|
|
|
|
static_assert(!ImplicitlyConstructible<std::jthread, Func<>>);
|
|
static_assert(!ImplicitlyConstructible<std::jthread, Func<int>, 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>(f)), get_stop_token(), auto(std::forward<Args>(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>(f)), auto(std::forward<Args>(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);
|
|
}
|
|
|
|
// Per https://eel.is/c++draft/thread.jthread.class#thread.jthread.cons-8:
|
|
//
|
|
// 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.
|
|
//
|
|
// 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;
|
|
}
|