sandbox
Loading...
Searching...
No Matches
delegate.hpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2022 Jonas Kabelitz
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 * You should have received a copy of the MIT License along with this program.
23 * If not, see <https://opensource.org/licenses/MIT/>.
24 */
25
30#ifndef LIBSBX_CORE_DELEGATE_HPP_
31#define LIBSBX_CORE_DELEGATE_HPP_
32
37#include <functional>
38#include <memory>
39#include <type_traits>
40#include <utility>
41
42#include <libsbx/core/concepts.hpp>
43
44#include <libsbx/memory/aligned_storage.hpp>
45
46namespace sbx::core {
47
49struct bad_delegate_call : std::runtime_error {
51 : std::runtime_error("bad_delegate_call") {}
52}; // struct bad_delegate_call
53
54template<typename Signature>
56
63template<typename Return, typename... Args>
64class delegate<Return(Args...)> {
65
66 using static_storage_type = memory::aligned_storage_t<sizeof(std::byte*), alignof(std::byte*)>;
67 using dynamic_storage_type = std::byte*;
68
69 union storage {
70 static_storage_type static_storage;
71 dynamic_storage_type dynamic_storage;
72 }; // union storage
73
74public:
75
79 delegate() noexcept
80 : _vtable{nullptr} { }
81
85 delegate(std::nullptr_t) noexcept
86 : _vtable{nullptr} { }
87
95 template<callable<Return, Args...> Callable>
96 requires (!std::is_same_v<std::remove_reference_t<Callable>, delegate>) // Dont allow other delegates here!
97 delegate(Callable&& callable)
98 : _vtable{_create_vtable<std::remove_reference_t<Callable>>()},
99 _storage{_create_storage<std::remove_reference_t<Callable>>(std::forward<std::remove_reference_t<Callable>>(callable))} { }
100
101 delegate(Return(*callable)(Args...))
102 : delegate{[callable](Args... args){ std::invoke(callable, std::forward<Args>(args)...); }} { }
103
104 template<typename Class>
105 delegate(Class& instance, Return(Class::*method)(Args...))
106 : delegate{_wrap_method(&instance, method)} { }
107
108 template<typename Class>
109 delegate(Class& instance, Return(Class::*method)(Args...)const)
110 : delegate{_wrap_method(&instance, method)} { }
111
112 template<typename Class>
113 delegate(const Class& instance, Return(Class::*method)(Args...)const)
114 : delegate{_wrap_method(&instance, method)} { }
115
116 delegate(const delegate& other)
117 : _vtable{other._vtable} {
118 if (_vtable) {
119 _vtable->copy(other._storage, _storage);
120 }
121 }
122
123 delegate(delegate&& other) noexcept
124 : _vtable{std::exchange(other._vtable, nullptr)} {
125 if (_vtable) {
126 _vtable->move(other._storage, _storage);
127 }
128 }
129
130 ~delegate() {
131 if (_vtable) {
132 _vtable->destroy(_storage);
133 }
134 }
135
136 delegate& operator=(const delegate& other) {
137 if (this != &other) {
138 if (_vtable) {
139 _vtable->destroy(_storage);
140 }
141
142 _vtable = other._vtable;
143
144 if (_vtable) {
145 _vtable->copy(other._storage, _storage);
146 }
147 }
148
149 return *this;
150 }
151
152 delegate& operator=(delegate&& other) noexcept {
153 if (this != &other) {
154 if (_vtable) {
155 _vtable->destroy(_storage);
156 }
157
158 _vtable = std::exchange(other._vtable, nullptr);
159
160 if (_vtable) {
161 _vtable->move(other._storage, _storage);
162 }
163 }
164
165 return *this;
166 }
167
168 auto invoke(Args&&... args) const {
169 if (!_vtable) {
170 throw std::runtime_error{"bad_delegate_call"};
171 }
172
173 return std::invoke(_vtable->invoke, _storage, std::forward<Args>(args)...);
174 }
175
176 Return operator()(Args&&... args) const {
177 return invoke(std::forward<Args>(args)...);
178 }
179
180 auto is_valid() const noexcept {
181 return _vtable != nullptr;
182 }
183
184 operator bool() const noexcept {
185 return is_valid();
186 }
187
188private:
189
190 template<typename Callable>
191 inline static constexpr auto requires_dynamic_allocation_v = !(std::is_nothrow_move_constructible_v<Callable> && sizeof(Callable) <= sizeof(static_storage_type) && alignof(Callable) <= alignof(static_storage_type));
192
193 template<typename Class>
194 auto _wrap_method(Class* instance, Return(Class::*method)(Args...)) {
195 return [instance, method](Args... args){ std::invoke(method, instance, std::forward<Args>(args)...); };
196 }
197
198 template<typename Class>
199 auto _wrap_method(Class* instance, Return(Class::*method)(Args...)const) {
200 return [instance, method](Args... args){ std::invoke(method, instance, std::forward<Args>(args)...); };
201 }
202
203 template<typename Class>
204 auto _wrap_method(const Class* instance, Return(Class::*method)(Args...)const) {
205 return [instance, method](Args... args){ std::invoke(method, instance, std::forward<Args>(args)...); };
206 }
207
208 struct vtable {
209 Return(*invoke)(const storage& storage, Args&&... args);
210 void(*copy)(const storage& source, storage& destination);
211 void(*move)(storage& source, storage& destination);
212 void(*destroy)(storage& storage);
213 };
214
215 template<typename Callable>
216 struct static_vtable {
217 static Return invoke(const storage& storage, Args&&... args) {
218 return std::invoke(reinterpret_cast<const Callable&>(storage.static_storage), std::forward<Args>(args)...);
219 }
220
221 static void copy(const storage& source, storage& destination) {
222 std::construct_at(reinterpret_cast<Callable*>(&destination.static_storage), reinterpret_cast<const Callable&>(source.static_storage));
223 }
224
225 static void move(storage& source, storage& destination) {
226 std::construct_at(reinterpret_cast<Callable*>(&destination.static_storage), std::move(reinterpret_cast<Callable&>(source.static_storage)));
227 destroy(source);
228 }
229
230 static void destroy(storage& storage) {
231 std::destroy_at(reinterpret_cast<Callable*>(&storage.static_storage));
232 }
233 };
234
235 template<typename Callable>
236 struct dynamic_vtable {
237 static Return invoke(const storage& storage, Args&&... args) {
238 return std::invoke(*reinterpret_cast<const Callable*>(storage.dynamic_storage), std::forward<Args>(args)...);
239 }
240
241 static void copy(const storage& source, storage& destination) {
242 destination.dynamic_storage = reinterpret_cast<dynamic_storage_type>(new Callable{*reinterpret_cast<Callable*>(source.dynamic_storage)});
243 }
244
245 static void move(storage& source, storage& destination) {
246 destination.dynamic_storage = source.dynamic_storage;
247 source.dynamic_storage = nullptr;
248 }
249
250 static void destroy(storage& storage) {
251 delete reinterpret_cast<Callable*>(storage.dynamic_storage);
252 }
253 };
254
255 template<typename Callable>
256 static vtable* _create_vtable() {
257 using vtable_type = std::conditional_t<requires_dynamic_allocation_v<Callable>, dynamic_vtable<Callable>, static_vtable<Callable>>;
258
259 static auto instance = vtable{
260 vtable_type::invoke,
261 vtable_type::copy,
262 vtable_type::move,
263 vtable_type::destroy
264 };
265
266 return &instance;
267 }
268
269 template<typename Callable>
270 storage _create_storage(Callable&& callable) {
271 if constexpr (requires_dynamic_allocation_v<Callable>) {
272 // switch from new to malloc/construct_at maybe
273 return storage{ .dynamic_storage = reinterpret_cast<dynamic_storage_type>(new Callable{std::forward<Callable>(callable)}) };
274 } else {
275 auto static_storage = static_storage_type{};
276 std::construct_at(reinterpret_cast<Callable*>(&static_storage), std::forward<Callable>(callable));
277 return storage{ .static_storage = static_storage };
278 }
279 }
280
281 vtable* _vtable{};
282 mutable storage _storage{};
283
284}; // class delegate
285
286} // namespace sbx::core
287
288#endif // LIBSBX_CORE_DELEGATE_HPP_
delegate(Callable &&callable)
Construct a delegate from a functor type.
Definition: delegate.hpp:97
delegate() noexcept
Default constructor.
Definition: delegate.hpp:79
delegate(std::nullptr_t) noexcept
Constructs a delegate from a nullptr.
Definition: delegate.hpp:85
Definition: delegate.hpp:55
Describes a type or object that can be invoked with the give parameters and return the given type.
Definition: concepts.hpp:17
Exception type that is thrown when a delegate that does not hold a handle is invoked.
Definition: delegate.hpp:49