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