ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
move_only_function.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <functional>
5#include <memory>
6#include <new>
7#include <type_traits>
8#include <utility>
9
11{
12
13template <typename T>
14using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
15
16template <typename Signature, std::size_t InlineSize = 3 * sizeof(void*)>
18
19template <typename R, typename... Args, std::size_t InlineSize>
20class move_only_function<R(Args...), InlineSize>
21{
22 static constexpr std::size_t storage_size = InlineSize < sizeof(void*) ? sizeof(void*) : InlineSize;
23
24 struct operations
25 {
26 auto (*invoke)(void*, Args&&...) -> R;
27 void (*destroy)(void*) noexcept;
28 void (*move)(void*, void*) noexcept;
29 };
30
31 template <typename F>
32 static constexpr bool stores_inline = InlineSize != 0 && sizeof(F) <= storage_size && alignof(F) <= alignof(void*)
33 && std::is_nothrow_move_constructible_v<F>;
34
35 template <typename F>
36 static auto
37 table() noexcept -> operations const*
38 {
39 if constexpr (stores_inline<F>)
40 {
41 static constexpr operations value{ [](void* storage, Args&&... args) -> R
42 {
43 if constexpr (std::is_void_v<R>)
44 std::invoke(*static_cast<F*>(storage), std::forward<Args>(args)...);
45 else
46 return std::invoke(*static_cast<F*>(storage),
47 std::forward<Args>(args)...);
48 },
49 [](void* storage) noexcept { static_cast<F*>(storage)->~F(); },
50 [](void* destination, void* source) noexcept
51 {
52 ::new (destination) F(std::move(*static_cast<F*>(source)));
53 static_cast<F*>(source)->~F();
54 } };
55 return &value;
56 }
57 else
58 {
59 static constexpr operations value{ [](void* storage, Args&&... args) -> R
60 {
61 auto& callable = **std::launder(static_cast<F**>(storage));
62 if constexpr (std::is_void_v<R>)
63 std::invoke(callable, std::forward<Args>(args)...);
64 else
65 return std::invoke(callable, std::forward<Args>(args)...);
66 },
67 [](void* storage) noexcept
68 {
69 auto pointer = std::launder(static_cast<F**>(storage));
70 delete *pointer;
71 *pointer = nullptr;
72 },
73 [](void* destination, void* source) noexcept
74 {
75 auto source_pointer = std::launder(static_cast<F**>(source));
76 ::new (destination) F*(*source_pointer);
77 *source_pointer = nullptr;
78 } };
79 return &value;
80 }
81 }
82
83public:
84 move_only_function() noexcept = default;
85 move_only_function(std::nullptr_t) noexcept {} // NOLINT(google-explicit-constructor)
86
87 template <typename F, typename Value = std::decay_t<F>,
88 std::enable_if_t<!std::is_same_v<remove_cvref_t<F>, move_only_function>
89 && std::is_invocable_r_v<R, Value&, Args...>,
90 int> = 0>
91 move_only_function(F&& function) // NOLINT(google-explicit-constructor)
92 {
93 if constexpr (std::is_pointer_v<Value> || std::is_member_pointer_v<Value>)
94 if (function == nullptr)
95 return;
96
97 operations_ = table<Value>();
98 if constexpr (stores_inline<Value>)
99 ::new (storage_) Value(std::forward<F>(function));
100 else
101 ::new (storage_) Value*(new Value(std::forward<F>(function)));
102 }
103
106
107 move_only_function(move_only_function&& other) noexcept : operations_(other.operations_)
108 {
109 if (operations_ != nullptr)
110 {
111 operations_->move(storage_, other.storage_);
112 other.operations_ = nullptr;
113 }
114 }
115
116 auto
118 {
119 if (this != &other)
120 {
121 reset();
122 operations_ = other.operations_;
123 if (operations_ != nullptr)
124 {
125 operations_->move(storage_, other.storage_);
126 other.operations_ = nullptr;
127 }
128 }
129 return *this;
130 }
131
133 {
134 reset();
135 }
136
137 explicit
138 operator bool() const noexcept
139 {
140 return operations_ != nullptr;
141 }
142
143 auto
144 operator()(Args... args) -> R
145 {
146 if (operations_ == nullptr)
147 throw std::bad_function_call();
148 if constexpr (std::is_void_v<R>)
149 operations_->invoke(storage_, std::forward<Args>(args)...);
150 else
151 return operations_->invoke(storage_, std::forward<Args>(args)...);
152 }
153
154 void
155 reset() noexcept
156 {
157 if (operations_ != nullptr)
158 {
159 operations_->destroy(storage_);
160 operations_ = nullptr;
161 }
162 }
163
164private:
165 operations const* operations_{ nullptr };
166 alignas(void*) unsigned char storage_[storage_size]{};
167};
168
169template <typename Signature, std::size_t InlineSize = 3 * sizeof(void*), typename Callable>
170[[nodiscard]] auto
172{
173 return move_only_function<Signature, InlineSize>(std::forward<Callable>(callable));
174}
175
176} // namespace threadschedule::detail
auto operator=(move_only_function const &) -> move_only_function &=delete
auto operator=(move_only_function &&other) noexcept -> move_only_function &
Aggregates multiple thread_registry_backend instances into a single queryable view.
@ other
Standard round-robin time-sharing.
auto make_move_only_function(Callable &&callable) -> move_only_function< Signature, InlineSize >
std::remove_cv_t< std::remove_reference_t< T > > remove_cvref_t