9#ifndef STLAB_CONCURRENCY_TASK_HPP
10#define STLAB_CONCURRENCY_TASK_HPP
45#include <stlab/execution/config.hpp>
50STLAB_EXECUTION_VERSION_NAMESPACE_BEGIN()
65 using dtor_t = void (*)(
void*)
noexcept;
66 using move_ctor_t = void (*)(
void*,
void*)
noexcept;
67 using target_type_t =
const std::type_info& (*)()
noexcept;
68 using pointer_t =
void* (*)(
void*)
noexcept;
69 using const_pointer_t =
const void* (*)(
const void*)
noexcept;
72 move_ctor_t move_ctor;
73 target_type_t target_type;
75 const_pointer_t const_pointer;
80 std::max(
alignof(std::max_align_t) * 2,
sizeof(
void*) * 8) -
81 std::max(
alignof(std::max_align_t),
sizeof(
void*) * 2);
86namespace execution_detail {
89template <std::
size_t Size, std::
size_t Alignment>
90struct task_storage_abi_guard {
91 STLAB_CORE_API
static const unsigned char value;
95using current_task_storage_abi_guard =
96 task_storage_abi_guard<stlab_v2_task_storage_size, stlab_v2_task_storage_alignment>;
101 STLAB_CORE_API
static const unsigned char value;
105constexpr auto stlab_v2_align_offset(std::size_t offset, std::size_t alignment)
noexcept
107 return (offset + alignment - 1) / alignment * alignment;
110inline constexpr auto stlab_v2_task_concept_move_ctor_offset = stlab_v2_align_offset(
111 sizeof(stlab_v2_task_concept::dtor_t),
alignof(stlab_v2_task_concept::move_ctor_t));
112inline constexpr auto stlab_v2_task_concept_target_type_offset = stlab_v2_align_offset(
113 stlab_v2_task_concept_move_ctor_offset +
sizeof(stlab_v2_task_concept::move_ctor_t),
114 alignof(stlab_v2_task_concept::target_type_t));
115inline constexpr auto stlab_v2_task_concept_pointer_offset = stlab_v2_align_offset(
116 stlab_v2_task_concept_target_type_offset +
sizeof(stlab_v2_task_concept::target_type_t),
117 alignof(stlab_v2_task_concept::pointer_t));
118inline constexpr auto stlab_v2_task_concept_const_pointer_offset = stlab_v2_align_offset(
119 stlab_v2_task_concept_pointer_offset +
sizeof(stlab_v2_task_concept::pointer_t),
120 alignof(stlab_v2_task_concept::const_pointer_t));
121inline constexpr auto stlab_v2_task_concept_alignment = std::max(
122 {
alignof(stlab_v2_task_concept::dtor_t),
alignof(stlab_v2_task_concept::move_ctor_t),
123 alignof(stlab_v2_task_concept::target_type_t),
alignof(stlab_v2_task_concept::pointer_t),
124 alignof(stlab_v2_task_concept::const_pointer_t)});
130static_assert(std::is_standard_layout_v<stlab_v2_task_concept>);
131static_assert(
alignof(stlab_v2_task_concept) == execution_detail::stlab_v2_task_concept_alignment);
132static_assert(std::is_same_v<
decltype(stlab_v2_task_concept::dtor), stlab_v2_task_concept::dtor_t>);
134 std::is_same_v<
decltype(stlab_v2_task_concept::move_ctor), stlab_v2_task_concept::move_ctor_t>);
135static_assert(std::is_same_v<
decltype(stlab_v2_task_concept::target_type),
136 stlab_v2_task_concept::target_type_t>);
138 std::is_same_v<
decltype(stlab_v2_task_concept::pointer), stlab_v2_task_concept::pointer_t>);
139static_assert(std::is_same_v<
decltype(stlab_v2_task_concept::const_pointer),
140 stlab_v2_task_concept::const_pointer_t>);
141static_assert(offsetof(stlab_v2_task_concept, dtor) == 0);
142static_assert(offsetof(stlab_v2_task_concept, move_ctor) ==
143 execution_detail::stlab_v2_task_concept_move_ctor_offset);
144static_assert(offsetof(stlab_v2_task_concept, target_type) ==
145 execution_detail::stlab_v2_task_concept_target_type_offset);
146static_assert(offsetof(stlab_v2_task_concept, pointer) ==
147 execution_detail::stlab_v2_task_concept_pointer_offset);
148static_assert(offsetof(stlab_v2_task_concept, const_pointer) ==
149 execution_detail::stlab_v2_task_concept_const_pointer_offset);
150static_assert(
sizeof(stlab_v2_task_concept) ==
151 execution_detail::stlab_v2_align_offset(
152 execution_detail::stlab_v2_task_concept_const_pointer_offset +
153 sizeof(stlab_v2_task_concept::const_pointer_t),
154 alignof(stlab_v2_task_concept)));
163template <
bool NoExcept,
class R,
class... Args>
172 using invoke_t = R (*)(
void*, Args...)
noexcept(NoExcept);
176 constexpr static bool maybe_empty =
177 std::is_pointer_v<std::decay_t<F>> || std::is_member_pointer_v<std::decay_t<F>> ||
178 std::is_same_v<std::function<R(Args...)>, std::decay_t<F>>;
181 constexpr static auto is_empty(
const F& f) -> std::enable_if_t<maybe_empty<F>,
bool> {
186 constexpr static auto is_empty(
const F&) -> std::enable_if_t<!maybe_empty<F>,
bool> {
190 template <
class F,
bool Small>
194 struct model<F, true> {
196 model(G&& f) : _f(std::forward<G>(f)) {}
197 model(model&&) noexcept = delete;
199 static
void dtor(
void* self) noexcept {
static_cast<model*
>(self)->~model(); }
200 static void move_ctor(
void* self,
void* p)
noexcept {
201 new (p) model(std::move(
static_cast<model*
>(self)->_f));
212 static auto invoke(
void* self, Args... args)
noexcept(NoExcept) -> R {
213 return std::invoke(
static_cast<model*
>(self)->_f, std::forward<Args>(args)...);
216 static auto target_type() noexcept -> const std::type_info& {
return typeid(F); }
217 static auto pointer(
void* self)
noexcept ->
void* {
return &
static_cast<model*
>(self)->_f; }
218 static auto const_pointer(
const void* self)
noexcept ->
const void* {
219 return &
static_cast<const model*
>(self)->_f;
221#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
225 static constexpr concept_t _vtable = {dtor, move_ctor, target_type, pointer, const_pointer};
226 static constexpr invoke_t _invoke = invoke;
232 struct model<F, false> {
234 model(G&& f) : _p(std::make_unique<F>(std::forward<G>(f))) {}
235 model(model&&) noexcept = default;
237 static
void dtor(
void* self) noexcept {
static_cast<model*
>(self)->~model(); }
238 static void move_ctor(
void* self,
void* p)
noexcept {
239 new (p) model(std::move(*
static_cast<model*
>(self)));
249 static auto invoke(
void* self, Args... args)
noexcept(NoExcept) -> R {
250 return std::invoke(*
static_cast<model*
>(self)->_p, std::forward<Args>(args)...);
253 static auto target_type() noexcept -> const std::type_info& {
return typeid(F); }
254 static auto pointer(
void* self)
noexcept ->
void* {
255 return static_cast<model*
>(self)->_p.get();
257 static auto const_pointer(
const void* self)
noexcept ->
const void* {
258 return static_cast<const model*
>(self)->_p.get();
261#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
265 static constexpr concept_t _vtable = {dtor, move_ctor, target_type, pointer, const_pointer};
266 static constexpr invoke_t _invoke = invoke;
269 std::unique_ptr<F> _p;
273 static void dtor(
void*)
noexcept {}
274 static void move_ctor(
void*,
void*)
noexcept {}
276 static auto invoke(
void*, Args...) noexcept(NoExcept) -> R {
277 if constexpr (NoExcept) {
279 throw std::bad_function_call();
284 throw std::bad_function_call();
287 static auto target_type_() noexcept -> const std::type_info& {
return typeid(void); }
288 static auto pointer(
void*)
noexcept ->
void* {
return nullptr; }
289 static auto const_pointer(
const void*)
noexcept ->
const void* {
return nullptr; }
291#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
294 static constexpr concept_t _vtable = {dtor, move_ctor, target_type_, pointer, const_pointer};
316 std::array<unsigned char, stlab_v2_task_storage_size> _model;
319 using result_type = R;
343 _vtable_ptr(vtable_ptr), _invoke(invoke_fn) {
344 vtable_ptr->move_ctor(source, &_model);
347 constexpr task_() noexcept = default;
348 constexpr
task_(std::nullptr_t) noexcept :
task_() {}
351 task_(
task_&& x) noexcept : _vtable_ptr(x._vtable_ptr), _invoke(x._invoke) {
352 _vtable_ptr->move_ctor(&x._model, &_model);
356 std::enable_if_t<!NoExcept || std::is_nothrow_invocable_v<F, Args...>,
bool> =
true>
358 using small_t = model<std::decay_t<F>,
true>;
359 using large_t = model<std::decay_t<F>,
false>;
363 std::is_nothrow_move_constructible_v<std::decay_t<F>>,
366 if (is_empty(f))
return;
368 new (&_model) model_t(std::forward<F>(f));
369 _vtable_ptr = &model_t::_vtable;
370 _invoke = &model_t::invoke;
373 ~task_() { _vtable_ptr->dtor(&_model); };
375 auto operator=(
const task_&) ->
task_& =
delete;
377 auto operator=(
task_&& x)
noexcept ->
task_& {
378 if (
this == &x)
return *
this;
379 _vtable_ptr->dtor(&_model);
380 _vtable_ptr = x._vtable_ptr;
382 _vtable_ptr->move_ctor(&x._model, &_model);
386 auto operator=(std::nullptr_t)
noexcept ->
task_& {
return *
this =
task_(); }
389 auto operator=(F&& f)
390 -> std::enable_if_t<!NoExcept || std::is_nothrow_invocable_v<
decltype(f), Args...>,
392 return *
this =
task_(std::forward<F>(f));
395 void swap(
task_& x)
noexcept { std::swap(*
this, x); }
397 explicit operator bool()
const {
return _vtable_ptr->const_pointer(&_model) !=
nullptr; }
399 [[nodiscard]]
auto target_type() const noexcept -> const std::type_info& {
400 return _vtable_ptr->target_type();
404 auto target() -> T* {
405 return (target_type() ==
typeid(T)) ?
static_cast<T*
>(_vtable_ptr->pointer(&_model)) :
410 [[nodiscard]]
auto target() const -> const T* {
411 return (target_type() ==
typeid(T)) ?
412 static_cast<const T*
>(_vtable_ptr->const_pointer(&_model)) :
416 template <
class... Brgs>
417 auto operator()(Brgs&&... brgs)
noexcept(NoExcept) -> R {
418 return _invoke(&_model, std::forward<Brgs>(brgs)...);
421 friend inline void swap(
task_& x,
task_& y)
noexcept {
return x.swap(y); }
422 friend inline auto operator==(
const task_& x, std::nullptr_t) ->
bool {
423 return !
static_cast<bool>(x);
425 friend inline auto operator==(std::nullptr_t,
const task_& x) ->
bool {
426 return !
static_cast<bool>(x);
428 friend inline auto operator!=(
const task_& x, std::nullptr_t) ->
bool {
429 return static_cast<bool>(x);
431 friend inline auto operator!=(std::nullptr_t,
const task_& x) ->
bool {
432 return static_cast<bool>(x);
438template <
template <bool,
class,
class...>
class T,
class F>
441template <
template <bool,
class,
class...>
class T,
class R,
class... Args>
443 using type = T<
false, R, Args...>;
446template <
template <bool,
class,
class...>
class T,
class R,
class... Args>
448 using type = T<
true, R, Args...>;
459STLAB_EXECUTION_VERSION_NAMESPACE_END()
Type-erased, move-only callable with signature R(Args...) (or noexcept variant).
Definition task.hpp:164
auto relocation_invoke() const noexcept -> invoke_t
Returns the function used to invoke this task's target.
Definition task.hpp:328
R(*)(void *, Args...) noexcept(NoExcept) invoke_t
Function used to invoke a task's target, valid for as long as the target is live.
Definition task.hpp:172
task_(const concept_t *vtable_ptr, invoke_t invoke_fn, void *source) noexcept
Constructs a task by relocating the target described by (vtable_ptr, invoke_fn) out of source,...
Definition task.hpp:342
stlab_v2_task_concept concept_t
Stable vtable describing how to move-construct, destroy, and introspect a task's target,...
Definition task.hpp:169
auto relocation_concept() const noexcept -> const concept_t *
Returns the vtable needed to relocate, invoke, or destroy this task's target without depending on its...
Definition task.hpp:323
auto relocation_source() noexcept -> void *
Returns the address of this task's inline model storage.
Definition task.hpp:333
constexpr std::size_t stlab_v2_task_storage_alignment
Alignment required by a v2 task's relocatable inline model.
Definition task.hpp:84
constexpr std::size_t stlab_v2_task_storage_size
Number of bytes reserved for a v2 task's relocatable inline model.
Definition task.hpp:79
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:453
Definition default_executor.hpp:49
Versioned operation table used to relocate a task target across the shared executor ABI.
Definition task.hpp:64