stlab 2.3.0
Modern, modular C++ algorithms, data structures, and concurrency primitives
Loading...
Searching...
No Matches
task.hpp
Go to the documentation of this file.
1/*
2 Copyright 2015 Adobe
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5*/
6
7/**************************************************************************************************/
8
9#ifndef STLAB_CONCURRENCY_TASK_HPP
10#define STLAB_CONCURRENCY_TASK_HPP
11
25
26/**************************************************************************************************/
27
28#include <algorithm>
29#include <array>
30#include <cstddef>
31#include <exception>
32#include <functional>
33#include <memory>
34#include <type_traits>
35#include <utility>
36
37#include <stlab/config.hpp>
38
39/**************************************************************************************************/
40
41namespace stlab {
42STLAB_VERSION_NAMESPACE_BEGIN()
43
44
52
53/**************************************************************************************************/
54
55
60template <bool NoExcept, class R, class... Args>
61class task_ {
62 template <class F>
63 constexpr static bool maybe_empty =
64 std::is_pointer_v<std::decay_t<F>> || std::is_member_pointer_v<std::decay_t<F>> ||
65 std::is_same_v<std::function<R(Args...)>, std::decay_t<F>>;
66
67 template <class F>
68 constexpr static auto is_empty(const F& f) -> std::enable_if_t<maybe_empty<F>, bool> {
69 return !f;
70 }
71
72 template <class F>
73 constexpr static auto is_empty(const F&) -> std::enable_if_t<!maybe_empty<F>, bool> {
74 return false;
75 }
76
77 struct concept_t {
78 void (*dtor)(void*) noexcept;
79 void (*move_ctor)(void*, void*) noexcept;
80 const std::type_info& (*target_type)() noexcept;
81 void* (*pointer)(void*) noexcept;
82 const void* (*const_pointer)(const void*) noexcept;
83 };
84
85 using invoke_t = R (*)(void*, Args...) noexcept(NoExcept);
86
87 template <class F, bool Small>
88 struct model;
89
90 template <class F>
91 struct model<F, true> {
92 template <class G> // for forwarding
93 model(G&& f) : _f(std::forward<G>(f)) {}
94 model(model&&) noexcept = delete;
95
96 static void dtor(void* self) noexcept { static_cast<model*>(self)->~model(); }
97 static void move_ctor(void* self, void* p) noexcept {
98 new (p) model(std::move(static_cast<model*>(self)->_f));
99 }
100
101 /*
102 NOTE (sean-parent): `Args` are _not_ universal references. This is a `concrete`
103 interface for the model. Do not add `&&`, that would make it an rvalue reference.
104 The `forward<Args>` here is correct. We are forwarding from the client defined
105 signature to the actual captured model.
106 */
107
108 // NOLINTNEXTLINE(performance-unnecessary-value-param)
109 static auto invoke(void* self, Args... args) noexcept(NoExcept) -> R {
110 return (static_cast<model*>(self)->_f)(std::forward<Args>(args)...);
111 }
112
113 static auto target_type() noexcept -> const std::type_info& { return typeid(F); }
114 static auto pointer(void* self) noexcept -> void* { return &static_cast<model*>(self)->_f; }
115 static auto const_pointer(const void* self) noexcept -> const void* {
116 return &static_cast<const model*>(self)->_f;
117 }
118#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
119 static const concept_t _vtable;
120 static const invoke_t _invoke;
121#else
122 static constexpr concept_t _vtable = {dtor, move_ctor, target_type, pointer, const_pointer};
123 static constexpr invoke_t _invoke = invoke;
124#endif
125 F _f;
126 };
127
128 template <class F>
129 struct model<F, false> {
130 template <class G> // for forwarding
131 model(G&& f) : _p(std::make_unique<F>(std::forward<G>(f))) {}
132 model(model&&) noexcept = default;
133
134 static void dtor(void* self) noexcept { static_cast<model*>(self)->~model(); }
135 static void move_ctor(void* self, void* p) noexcept {
136 new (p) model(std::move(*static_cast<model*>(self)));
137 }
138
139 /*
140 NOTE (sean-parent): `Args` are _not_ universal references. This is a `concrete`
141 interface for the model. Do not add `&&`, that would make it an rvalue reference.
142 The `forward<Args>` here is correct. We are forwarding from the client defined
143 signature to the actual captured model.
144 */
145
146 static auto invoke(void* self, Args... args) noexcept(NoExcept) -> R {
147 return (*static_cast<model*>(self)->_p)(std::forward<Args>(args)...);
148 }
149
150 static auto target_type() noexcept -> const std::type_info& { return typeid(F); }
151 static auto pointer(void* self) noexcept -> void* {
152 return static_cast<model*>(self)->_p.get();
153 }
154 static auto const_pointer(const void* self) noexcept -> const void* {
155 return static_cast<const model*>(self)->_p.get();
156 }
157
158#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
159 static const concept_t _vtable;
160 static const invoke_t _invoke;
161#else
162 static constexpr concept_t _vtable = {dtor, move_ctor, target_type, pointer, const_pointer};
163 static constexpr invoke_t _invoke = invoke;
164#endif
165
166 std::unique_ptr<F> _p;
167 };
168
169 // empty (default) vtable
170 static void dtor(void*) noexcept {}
171 static void move_ctor(void*, void*) noexcept {}
172 // NOLINTNEXTLINE(performance-unnecessary-value-param)
173 static auto invoke(void*, Args...) noexcept(NoExcept) -> R {
174 if constexpr (NoExcept) {
175 try {
176 throw std::bad_function_call();
177 } catch (...) {
178 std::terminate();
179 }
180 } else {
181 throw std::bad_function_call();
182 }
183 }
184 static auto target_type_() noexcept -> const std::type_info& { return typeid(void); }
185 static auto pointer(void*) noexcept -> void* { return nullptr; }
186 static auto const_pointer(const void*) noexcept -> const void* { return nullptr; }
187
188#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
189 static const concept_t _vtable;
190#else
191 static constexpr concept_t _vtable = {dtor, move_ctor, target_type_, pointer, const_pointer};
192#endif
193
194 /*
195 The layout of this object is going to be:
196 _vtable_ptr
197 _invoke
198 _model
199
200 Because the model is going to be max-aligned, it will leave a gap between the vtable_ptr and
201 the _model, we fill that gap by lifting the _invoke pointer into it from the vtable. This means
202 invoke calls require one less indirection.
203
204 The size of the model is big enough for 6 pointers or (2 max aligned object - 2 pointers)
205 (which would typically be 2 pointers). The rational here is that we want the total object size
206 to be a power of 2, for the object but the model store to be large enough to hold 2
207 weak-pointers (which is 4 pointers total), so we give things a little extra room.
208 */
209
210 static constexpr size_t max_align = alignof(std::max_align_t);
211 static constexpr size_t small_size =
212 std::max(max_align * 2, sizeof(void*) * 8) - std::max(max_align, sizeof(void*) * 2);
213
214 const concept_t* _vtable_ptr = &_vtable;
215 invoke_t _invoke = invoke;
216 alignas(std::max_align_t) std::array<unsigned char, small_size> _model;
217
218public:
219 using result_type = R;
220
221 constexpr task_() noexcept = default;
222 constexpr task_(std::nullptr_t) noexcept : task_() {}
223 task_(const task_&) = delete;
224 task_(const task_&&) = delete;
225 task_(task_&& x) noexcept : _vtable_ptr(x._vtable_ptr), _invoke(x._invoke) {
226 _vtable_ptr->move_ctor(&x._model, &_model);
227 }
228
229 template <class F,
230 std::enable_if_t<!NoExcept || std::is_nothrow_invocable_v<F, Args...>, bool> = true>
231 task_(F&& f) {
232 using small_t = model<std::decay_t<F>, true>;
233 using large_t = model<std::decay_t<F>, false>;
234 using model_t = std::conditional_t<(sizeof(small_t) <= small_size) &&
235 (alignof(small_t) <= alignof(decltype(_model))),
236 small_t, large_t>;
237
238 if (is_empty(f)) return;
239
240 new (&_model) model_t(std::forward<F>(f));
241 _vtable_ptr = &model_t::_vtable;
242 _invoke = &model_t::invoke;
243 }
244
245 ~task_() { _vtable_ptr->dtor(&_model); };
246
247 auto operator=(const task_&) -> task_& = delete;
248
249 auto operator=(task_&& x) noexcept -> task_& {
250 _vtable_ptr->dtor(&_model);
251 _vtable_ptr = x._vtable_ptr;
252 _invoke = x._invoke;
253 _vtable_ptr->move_ctor(&x._model, &_model);
254 return *this;
255 }
256
257 auto operator=(std::nullptr_t) noexcept -> task_& { return *this = task_(); }
258
259 template <class F>
260 auto operator=(F&& f)
261 -> std::enable_if_t<!NoExcept || std::is_nothrow_invocable_v<decltype(f), Args...>,
262 task_&> {
263 return *this = task_(std::forward<F>(f));
264 }
265
266 void swap(task_& x) noexcept { std::swap(*this, x); }
267
268 explicit operator bool() const { return _vtable_ptr->const_pointer(&_model) != nullptr; }
269
270 [[nodiscard]] auto target_type() const noexcept -> const std::type_info& {
271 return _vtable_ptr->target_type();
272 }
273
274 template <class T>
275 auto target() -> T* {
276 return (target_type() == typeid(T)) ? static_cast<T*>(_vtable_ptr->pointer(&_model)) :
277 nullptr;
278 }
279
280 template <class T>
281 [[nodiscard]] [[nodiscard]] [[nodiscard]] auto target() const -> const T* {
282 return (target_type() == typeid(T)) ?
283 static_cast<const T*>(_vtable_ptr->const_pointer(&_model)) :
284 nullptr;
285 }
286
287 template <class... Brgs>
288 auto operator()(Brgs&&... brgs) noexcept(NoExcept) {
289 return _invoke(&_model, std::forward<Brgs>(brgs)...);
290 }
291
292 friend inline void swap(task_& x, task_& y) noexcept { return x.swap(y); }
293 friend inline auto operator==(const task_& x, std::nullptr_t) -> bool {
294 return !static_cast<bool>(x);
295 }
296 friend inline auto operator==(std::nullptr_t, const task_& x) -> bool {
297 return !static_cast<bool>(x);
298 }
299 friend inline auto operator!=(const task_& x, std::nullptr_t) -> bool {
300 return static_cast<bool>(x);
301 }
302 friend inline auto operator!=(std::nullptr_t, const task_& x) -> bool {
303 return static_cast<bool>(x);
304 }
305};
306
307/**************************************************************************************************/
308
309template <template <bool, class, class...> class T, class F>
311
312template <template <bool, class, class...> class T, class R, class... Args>
313struct noexcept_deducer<T, R(Args...)> {
314 using type = T<false, R, Args...>;
315};
316
317template <template <bool, class, class...> class T, class R, class... Args>
318struct noexcept_deducer<T, R(Args...) noexcept> {
319 using type = T<true, R, Args...>;
320};
321
323template <class F>
325
326/**************************************************************************************************/
327
329
330STLAB_VERSION_NAMESPACE_END()
331} // namespace stlab
332
333/**************************************************************************************************/
334
335#endif
336
337/**************************************************************************************************/
Type-erased, move-only callable with signature R(Args...) (or noexcept variant).
Definition task.hpp:61
typename noexcept_deducer< task_, F >::type task
task_ with noexcept deduced from the function type F (e.g. void() vs void() noexcept).
Definition task.hpp:324
Definition reverse.hpp:28
Definition task.hpp:310