libstp
Robotics library for Botball
Loading...
Searching...
No Matches
jthread_compat.hpp
Go to the documentation of this file.
1#pragma once
2
3// std::jthread + std::stop_token availability is fragmented. GCC, MSVC, and
4// upstream libc++ 20 ship them, but Apple's libc++ (Xcode 16.4, clang 17) does
5// not declare the types at all — _LIBCPP_DISABLE_AVAILABILITY can't help
6// because the header is missing the symbols entirely.
7//
8// This compat layer aliases std:: on platforms that have the feature and
9// provides a minimal RAII-stopping fallback on Apple. The public ABI matches
10// what ThreadManager uses: jthread construction with a stop_token-taking
11// callable, request_stop(), joinable(), join(), and stop_token::stop_requested().
12
13#include <thread>
14
15#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L
16
17# include <stop_token>
18
20{
21 using std::jthread;
22 using std::stop_source;
23 using std::stop_token;
24}
25
26#else
27
28# include <atomic>
29# include <memory>
30# include <type_traits>
31# include <utility>
32
34{
38 {
39 public:
40 stop_token() = default;
41 explicit stop_token(std::shared_ptr<std::atomic<bool>> flag) noexcept
42 : flag_(std::move(flag))
43 {
44 }
45
46 [[nodiscard]] bool stop_requested() const noexcept
47 {
48 return flag_ && flag_->load(std::memory_order_acquire);
49 }
50
51 [[nodiscard]] bool stop_possible() const noexcept
52 {
53 return static_cast<bool>(flag_);
54 }
55
56 private:
57 std::shared_ptr<std::atomic<bool>> flag_;
58 };
59
62 {
63 public:
64 stop_source() : flag_(std::make_shared<std::atomic<bool>>(false)) {}
65
66 [[nodiscard]] stop_token get_token() const noexcept
67 {
68 return stop_token(flag_);
69 }
70
71 bool request_stop() noexcept
72 {
73 if (!flag_) return false;
74 bool expected = false;
75 return flag_->compare_exchange_strong(
76 expected, true, std::memory_order_release, std::memory_order_acquire);
77 }
78
79 [[nodiscard]] bool stop_requested() const noexcept
80 {
81 return flag_ && flag_->load(std::memory_order_acquire);
82 }
83
84 private:
85 std::shared_ptr<std::atomic<bool>> flag_;
86 };
87
90 class jthread
91 {
92 public:
93 jthread() noexcept = default;
94
95 template <typename F, typename... Args>
96 explicit jthread(F&& f, Args&&... args)
97 {
98 if constexpr (std::is_invocable_v<F&, stop_token, Args&...>)
99 {
100 thread_ = std::thread(
101 [fn = std::forward<F>(f), tok = source_.get_token()](
102 auto&&... a) mutable {
103 fn(tok, std::forward<decltype(a)>(a)...);
104 },
105 std::forward<Args>(args)...);
106 }
107 else
108 {
109 thread_ = std::thread(std::forward<F>(f), std::forward<Args>(args)...);
110 }
111 }
112
114 {
115 if (thread_.joinable())
116 {
117 source_.request_stop();
118 thread_.join();
119 }
120 }
121
122 jthread(const jthread&) = delete;
123 jthread& operator=(const jthread&) = delete;
124
125 jthread(jthread&& other) noexcept
126 : source_(std::move(other.source_)), thread_(std::move(other.thread_))
127 {
128 }
129
130 jthread& operator=(jthread&& other) noexcept
131 {
132 if (this != &other)
133 {
134 if (thread_.joinable())
135 {
136 source_.request_stop();
137 thread_.join();
138 }
139 source_ = std::move(other.source_);
140 thread_ = std::move(other.thread_);
141 }
142 return *this;
143 }
144
145 [[nodiscard]] bool joinable() const noexcept { return thread_.joinable(); }
146 void join() { thread_.join(); }
147 void detach() { thread_.detach(); }
148
149 [[nodiscard]] stop_source get_stop_source() const noexcept { return source_; }
150 [[nodiscard]] stop_token get_stop_token() const noexcept
151 {
152 return source_.get_token();
153 }
154 bool request_stop() noexcept { return source_.request_stop(); }
155
156 private:
157 stop_source source_{};
158 std::thread thread_{};
159 };
160}
161
162#endif
163
164// Re-export the active implementation directly under libstp::threading so the
165// rest of the codebase writes unqualified `jthread` / `stop_token` and stays
166// agnostic to whether std:: or the fallback is in use.
167namespace libstp::threading
168{
172}
stop_source get_stop_source() const noexcept
stop_token get_stop_token() const noexcept
jthread & operator=(jthread &&other) noexcept
jthread(const jthread &)=delete
jthread & operator=(const jthread &)=delete
jthread(jthread &&other) noexcept
Owner side of the stop signal. Setting it is sticky and atomic.
stop_token get_token() const noexcept
stop_token(std::shared_ptr< std::atomic< bool > > flag) noexcept
compat::stop_source stop_source
compat::stop_token stop_token
compat::jthread jthread