Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
delegate.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <concepts>
6#include <cstddef>
7#include <functional>
8#include <tuple>
9#include <type_traits>
10#include <utility>
11
12namespace helios {
13
14namespace details {
15
16template <typename T>
18
19template <typename R, typename... Args>
20struct TupleToFunctionSignature<std::tuple<R, Args...>> {
21 using Type = R(Args...);
22};
23
24template <typename FunctionSignature>
26
27template <typename R, typename... Args>
28struct FreeFunctionTraits<R (*)(Args...)> {
29 using ReturnType = R;
30 using Arguments = std::tuple<Args...>;
31};
32
33/// @brief Traits for member function pointers (non-const and const).
34template <typename FunctionSignature>
36
37template <typename C, typename R, typename... Args>
38struct MemberFunctionTraits<R (C::*)(Args...)> {
39 using Class = C;
40 using ReturnType = R;
41 using Arguments = std::tuple<Args...>;
42};
43
44template <typename C, typename R, typename... Args>
45struct MemberFunctionTraits<R (C::*)(Args...) const> {
46 using Class = const C;
47 using ReturnType = R;
48 using Arguments = std::tuple<Args...>;
49};
50
51} // namespace details
52
53/**
54 * @brief Type-erased callable wrapper for free and member functions.
55 * @details Delegate is a lightweight, non-owning wrapper for:
56 * - Free functions
57 * - Member functions (including const and virtual)
58 *
59 * It does not allocate and stores only:
60 * - A raw instance pointer (for member functions, may be null for free
61 * functions)
62 * - A function pointer to a small thunk that performs the actual call
63 *
64 * The delegate is intentionally minimal and exception-free.
65 * It returns default-constructed values when empty (for non-void return types)
66 * and is a no-op for void return types.
67 *
68 * @tparam FunctionSignature Function type in the form `R(Args...)`
69 */
70template <typename FunctionSignature>
72
73template <typename ReturnType, typename... Args>
74class Delegate<ReturnType(Args...)> {
75public:
76 using FunctionType = ReturnType (*)(void*, Args...);
77
78 /// @brief Default constructs an empty delegate.
79 constexpr Delegate() noexcept = default;
80 constexpr Delegate(const Delegate&) noexcept = default;
81 constexpr Delegate(Delegate&&) noexcept = default;
82 constexpr ~Delegate() noexcept = default;
83
84 constexpr Delegate& operator=(const Delegate&) noexcept = default;
85 constexpr Delegate& operator=(Delegate&&) noexcept = default;
86
87 /**
88 * @brief Create delegate from a free function pointer.
89 * @details Binds a free function with no instance pointer required.
90 * @tparam Func Free function pointer value
91 * @return Delegate bound to the given free function
92 */
93 template <auto Func>
94 requires(
95 !std::is_member_function_pointer_v<decltype(Func)> &&
96 std::same_as<ReturnType, typename details::FreeFunctionTraits<
97 decltype(Func)>::ReturnType> &&
98 (sizeof...(Args) ==
99 std::tuple_size_v<
100 typename details::FreeFunctionTraits<decltype(Func)>::Arguments>))
101 static constexpr Delegate From() noexcept;
102
103 /**
104 * @brief Create delegate from a free function pointer with explicit signature
105 * type.
106 * @details Variant that uses an explicit signature type instead of auto
107 * non-type template.
108 * @tparam Signature Free function pointer type
109 * @tparam Func Free function pointer value
110 * @return Delegate bound to the given free function
111 */
112 template <typename Signature, Signature Func>
113 requires(!std::is_member_function_pointer_v<decltype(Func)> &&
114 std::same_as<ReturnType, typename details::FreeFunctionTraits<
115 Signature>::ReturnType> &&
116 (sizeof...(Args) ==
117 std::tuple_size_v<
118 typename details::FreeFunctionTraits<Signature>::Arguments>))
119 static constexpr Delegate From() noexcept;
120
121 /**
122 * @brief Create delegate from a member function pointer.
123 * @details Binds a non-const or const member function pointer to a specific
124 * instance.
125 * @warning Instance reference must remain valid for the lifetime of the
126 * delegate.
127 * @tparam Func Member function pointer
128 * @param instance Reference to the object instance used for invocation
129 * @return Delegate bound to the given member function and instance
130 */
131 template <auto Func>
132 requires std::is_member_function_pointer_v<decltype(Func)> &&
133 std::same_as<ReturnType, typename details::MemberFunctionTraits<
134 decltype(Func)>::ReturnType> &&
135 (sizeof...(Args) ==
136 std::tuple_size_v<typename details::MemberFunctionTraits<
137 decltype(Func)>::Arguments>)
138 static constexpr Delegate From(
139 typename details::MemberFunctionTraits<decltype(Func)>::Class&
140 instance) noexcept;
141
142 /**
143 * @brief Create delegate from a member function pointer with explicit
144 * signature type.
145 * @details Variant that uses an explicit signature type instead of auto
146 * non-type template.
147 * @warning Instance reference must remain valid for the lifetime of the
148 * delegate.
149 * @tparam Signature Member function pointer type
150 * @tparam Func Member function pointer value
151 * @param instance Reference to the object instance used for invocation
152 * @return Delegate bound to the given member function and instance
153 */
154 template <typename Signature, Signature Func>
155 requires std::is_member_function_pointer_v<decltype(Func)> &&
156 std::same_as<ReturnType, typename details::MemberFunctionTraits<
157 Signature>::ReturnType> &&
158 (sizeof...(Args) ==
159 std::tuple_size_v<
160 typename details::MemberFunctionTraits<Signature>::Arguments>)
161 static constexpr Delegate From(
162 typename details::MemberFunctionTraits<Signature>::Class&
163 instance) noexcept;
164
165 /// @brief Reset delegate to empty state.
166 constexpr void Reset() noexcept;
167
168 /**
169 * @brief Invoke delegate with exact argument types.
170 * @details If delegate is empty, returns default constructed ReturnType for
171 * non-void return types and does nothing for void return type.
172 * @warning If delegate is empty and ReturnType is not default constructible
173 * then using the returned value is UB.
174 * @param args Arguments to forward to the bound callable
175 * @return Result of the invocation or default constructed value for empty
176 * delegate
177 */
178 constexpr ReturnType Invoke(Args&&... args) const
179 noexcept(std::is_nothrow_invocable_v<FunctionType, void*, Args&&...>);
180
181 /**
182 * @brief Invoke delegate with polymorphically convertible argument types.
183 * @details This overload allows passing arguments that are convertible
184 * or have base/derived relationship with the delegate's arguments.
185 * @warning If delegate is empty and ReturnType is not default constructible
186 * then using the returned value is UB.
187 * @tparam UArgs Parameter pack of actual argument types
188 * @param args Arguments to forward to the bound callable
189 * @return Result of the invocation or default constructed value for empty
190 * delegate
191 */
192 template <typename... UArgs>
193 requires(sizeof...(UArgs) == sizeof...(Args)) &&
194 (... && utils::PolymorphicConvertible<UArgs, Args>)
195 constexpr ReturnType Invoke(UArgs&&... args) const
196 noexcept(std::is_nothrow_invocable_v<FunctionType, void*, UArgs...>);
197
198 /**
199 * @brief Invoke delegate with exact argument types.
200 * @details Forwarding operator to Invoke.
201 * @warning Same as for Invoke: empty delegate returns default constructed
202 * value for non-void return type.
203 * @param args Arguments to forward to the bound callable
204 * @return Result of the invocation or default constructed value for empty
205 * delegate
206 */
207 constexpr ReturnType operator()(Args&&... args) const
208 noexcept(std::is_nothrow_invocable_v<FunctionType, void*, Args...>) {
209 return Invoke(std::forward<Args>(args)...);
210 }
211
212 /**
213 * @brief Invoke delegate with polymorphically convertible argument types.
214 * @details Forwarding operator to Invoke for flexible argument passing.
215 * @warning Same as for Invoke: empty delegate returns default constructed
216 * value for non-void return type.
217 * @tparam UArgs Parameter pack of actual argument types
218 * @param args Arguments to forward to the bound callable
219 * @return Result of the invocation or default constructed value for empty
220 * delegate
221 */
222 template <typename... UArgs>
223 requires(sizeof...(UArgs) == sizeof...(Args)) &&
225 constexpr ReturnType operator()(UArgs&&... args) const
226 noexcept(std::is_nothrow_invocable_v<FunctionType, void*, UArgs...>) {
227 return Invoke(std::forward<UArgs>(args)...);
228 }
229
230 /**
231 * @brief Compare two delegates for equality.
232 * @details Delegates are equal if they have the same instance and thunk
233 * pointer.
234 * @param other Delegate to compare with
235 * @return True if equal, false otherwise
236 */
237 constexpr bool operator==(const Delegate& other) const noexcept {
238 return instance_ptr_ == other.instance_ptr_ &&
239 function_ptr_ == other.function_ptr_;
240 }
241
242 /**
243 * @brief Compare two delegates for inequality.
244 * @param other Delegate to compare with
245 * @return True if not equal, false otherwise
246 */
247 constexpr bool operator!=(const Delegate& other) const noexcept {
248 return !(*this == other);
249 }
250
251 /**
252 * @brief Check if delegate is bound to a callable.
253 * @return True if delegate is non-empty, false otherwise
254 */
255 [[nodiscard]] constexpr bool Valid() const noexcept {
256 return function_ptr_ != nullptr;
257 }
258
259 /**
260 * @brief Get raw instance pointer stored inside delegate.
261 * @details For free functions this will be nullptr.
262 * For member functions this points to the bound object instance.
263 * @return Raw instance pointer
264 */
265 [[nodiscard]] constexpr void* InstancePtr() const noexcept {
266 return instance_ptr_;
267 }
268
269private:
270 void* instance_ptr_ = nullptr;
271 FunctionType function_ptr_ = nullptr;
272};
273
274template <typename ReturnType, typename... Args>
275constexpr void Delegate<ReturnType(Args...)>::Reset() noexcept {
276 instance_ptr_ = nullptr;
277 function_ptr_ = nullptr;
278}
279
280template <typename ReturnType, typename... Args>
281constexpr auto Delegate<ReturnType(Args...)>::Invoke(Args&&... args) const
282 noexcept(std::is_nothrow_invocable_v<FunctionType, void*, Args&&...>)
283 -> ReturnType {
284 if (function_ptr_ == nullptr) [[unlikely]] {
285 if constexpr (std::is_void_v<ReturnType>) {
286 return;
287 } else {
288 return {};
289 }
290 }
291
292 if constexpr (std::is_void_v<ReturnType>) {
293 std::invoke(function_ptr_, instance_ptr_, std::forward<Args>(args)...);
294 return;
295 } else {
296 return std::invoke(function_ptr_, instance_ptr_,
297 std::forward<Args>(args)...);
298 }
299}
300
301template <typename ReturnType, typename... Args>
302template <typename... UArgs>
303 requires(sizeof...(UArgs) == sizeof...(Args)) &&
305constexpr auto Delegate<ReturnType(Args...)>::Invoke(UArgs&&... args) const
306 noexcept(std::is_nothrow_invocable_v<FunctionType, void*, UArgs...>)
307 -> ReturnType {
308 if (function_ptr_ == nullptr) [[unlikely]] {
309 if constexpr (std::is_void_v<ReturnType>) {
310 return;
311 } else {
312 return {};
313 }
314 }
315
316 if constexpr (std::is_void_v<ReturnType>) {
317 std::invoke(function_ptr_, instance_ptr_, std::forward<UArgs>(args)...);
318 return;
319 } else {
320 return std::invoke(function_ptr_, instance_ptr_,
321 std::forward<UArgs>(args)...);
322 }
323}
324
325template <typename ReturnType, typename... Args>
326template <auto Func>
327 requires(
328 !std::is_member_function_pointer_v<decltype(Func)> &&
329 std::same_as<ReturnType, typename details::FreeFunctionTraits<
330 decltype(Func)>::ReturnType> &&
331 (sizeof...(Args) ==
332 std::tuple_size_v<
333 typename details::FreeFunctionTraits<decltype(Func)>::Arguments>))
334constexpr auto Delegate<ReturnType(Args...)>::From() noexcept -> Delegate {
335 using Traits = details::FreeFunctionTraits<decltype(Func)>;
336 using Arguments = typename Traits::Arguments;
337
338 static_assert(
339 []<size_t... I>(std::index_sequence<I...>) {
340 return (... && utils::PolymorphicConvertible<
341 Args, std::tuple_element_t<I, Arguments>>);
342 }(std::make_index_sequence<sizeof...(Args)>{}),
343 "Arguments must be convertible or have base-derived relationship");
344
345 Delegate delegate;
346 delegate.function_ptr_ =
347 [](void* /*instance*/, Args... call_args) noexcept(
348 std::is_nothrow_invocable_v<FunctionType, void*, Args...>)
349 -> ReturnType {
350 if constexpr (std::is_void_v<ReturnType>) {
351 std::invoke(Func,
352 static_cast<typename std::tuple_element_t<0, Arguments>>(
353 call_args)...);
354 return;
355 } else {
356 return std::invoke(
357 Func, static_cast<typename std::tuple_element_t<0, Arguments>>(
358 call_args)...);
359 }
360 };
361
362 return delegate;
363}
364
365template <typename ReturnType, typename... Args>
366template <typename Signature, Signature Func>
367 requires(!std::is_member_function_pointer_v<decltype(Func)> &&
368 std::same_as<ReturnType, typename details::FreeFunctionTraits<
369 Signature>::ReturnType> &&
370 (sizeof...(Args) ==
371 std::tuple_size_v<
373constexpr auto Delegate<ReturnType(Args...)>::From() noexcept -> Delegate {
375 using Arguments = typename Traits::Arguments;
376
377 static_assert(
378 []<size_t... I>(std::index_sequence<I...>) {
379 return (... && utils::PolymorphicConvertible<
380 Args, std::tuple_element_t<I, Arguments>>);
381 }(std::make_index_sequence<sizeof...(Args)>{}),
382 "Arguments must be convertible or have base-derived relationship");
383
384 Delegate delegate;
385 delegate.function_ptr_ =
386 [](void* /*instance*/, Args... call_args) noexcept(
387 std::is_nothrow_invocable_v<FunctionType, void*, Args...>)
388 -> ReturnType {
389 if constexpr (std::is_void_v<ReturnType>) {
390 std::invoke(Func,
391 static_cast<typename std::tuple_element_t<0, Arguments>>(
392 call_args)...);
393 return;
394 } else {
395 return std::invoke(
396 Func, static_cast<typename std::tuple_element_t<0, Arguments>>(
397 call_args)...);
398 }
399 };
400
401 return delegate;
402}
403
404template <typename ReturnType, typename... Args>
405template <auto Func>
406 requires std::is_member_function_pointer_v<decltype(Func)> &&
407 std::same_as<ReturnType, typename details::MemberFunctionTraits<
408 decltype(Func)>::ReturnType> &&
409 (sizeof...(Args) ==
410 std::tuple_size_v<typename details::MemberFunctionTraits<
411 decltype(Func)>::Arguments>)
412constexpr auto Delegate<ReturnType(Args...)>::From(
413 typename details::MemberFunctionTraits<decltype(Func)>::Class&
414 instance) noexcept -> Delegate {
415 using Traits = details::MemberFunctionTraits<decltype(Func)>;
416 using Class = typename Traits::Class;
417 using Arguments = typename Traits::Arguments;
418
419 static_assert(
420 []<size_t... I>(std::index_sequence<I...>) {
421 return (... && utils::PolymorphicConvertible<
422 Args, std::tuple_element_t<I, Arguments>>);
423 }(std::make_index_sequence<sizeof...(Args)>{}),
424 "Arguments must be convertible or have base-derived relationship");
425
426 Delegate delegate;
427 delegate.instance_ptr_ = &const_cast<std::remove_const_t<Class>&>(instance);
428 delegate.function_ptr_ =
429 [](void* instance_ptr, Args... call_args) noexcept(
430 std::is_nothrow_invocable_v<FunctionType, void*, Args...>)
431 -> ReturnType {
432 auto* typed_instance = static_cast<Class*>(instance_ptr);
433
434 if constexpr (std::is_void_v<ReturnType>) {
435 std::invoke(Func, typed_instance,
436 static_cast<typename std::tuple_element_t<0, Arguments>>(
437 call_args)...);
438 return;
439 } else {
440 return std::invoke(
441 Func, typed_instance,
442 static_cast<typename std::tuple_element_t<0, Arguments>>(
443 call_args)...);
444 }
445 };
446
447 return delegate;
448}
449
450template <typename ReturnType, typename... Args>
451template <typename Signature, Signature Func>
452 requires std::is_member_function_pointer_v<decltype(Func)> &&
453 std::same_as<ReturnType, typename details::MemberFunctionTraits<
454 Signature>::ReturnType> &&
455 (sizeof...(Args) ==
456 std::tuple_size_v<
458constexpr auto Delegate<ReturnType(Args...)>::From(
459 typename details::MemberFunctionTraits<Signature>::Class& instance) noexcept
460 -> Delegate {
462 using Class = typename Traits::Class;
463 using Arguments = typename Traits::Arguments;
464
465 static_assert(
466 []<size_t... I>(std::index_sequence<I...>) {
467 return (... && utils::PolymorphicConvertible<
468 Args, std::tuple_element_t<I, Arguments>>);
469 }(std::make_index_sequence<sizeof...(Args)>{}),
470 "Arguments must be convertible or have base-derived relationship");
471
472 Delegate delegate;
473 delegate.instance_ptr_ = &const_cast<std::remove_const_t<Class>&>(instance);
474 delegate.function_ptr_ =
475 [](void* instance_ptr, Args... call_args) noexcept(
476 std::is_nothrow_invocable_v<FunctionType, void*, Args...>)
477 -> ReturnType {
478 auto* typed_instance = static_cast<Class*>(instance_ptr);
479 if constexpr (std::is_void_v<ReturnType>) {
480 std::invoke(Func, typed_instance,
481 static_cast<typename std::tuple_element_t<0, Arguments>>(
482 call_args)...);
483
484 return;
485 } else {
486 return std::invoke(
487 Func, typed_instance,
488 static_cast<typename std::tuple_element_t<0, Arguments>>(
489 call_args)...);
490 }
491 };
492
493 return delegate;
494}
495
496/**
497 * @brief Helper to create delegate from free function pointer.
498 * @tparam Func Free function pointer.
499 * @return Delegate bound to the given free function.
500 */
501template <auto Func>
502 requires(!std::is_member_function_pointer_v<decltype(Func)>)
503constexpr auto MakeDelegate() noexcept {
504 using Traits = details::FreeFunctionTraits<decltype(Func)>;
505 using ReturnType = typename Traits::ReturnType;
506 using Args = typename Traits::Arguments;
507
508 return []<size_t... I>(std::index_sequence<I...>) {
509 return Delegate<ReturnType(
510 std::tuple_element_t<I, Args>...)>::template From<Func>();
511 }(std::make_index_sequence<std::tuple_size_v<Args>>{});
512}
513
514/**
515 * @brief Helper to create delegate from member function pointer.
516 * @tparam Func Member function pointer.
517 * @param instance Reference to the object instance used for invocation.
518 * @return Delegate bound to the given member function and instance.
519 */
520template <auto Func>
521 requires std::is_member_function_pointer_v<decltype(Func)>
522constexpr auto MakeDelegate(
523 typename details::MemberFunctionTraits<decltype(Func)>::Class&
524 instance) noexcept {
525 using Traits = details::MemberFunctionTraits<decltype(Func)>;
526 using ReturnType = typename Traits::ReturnType;
527 using Args = typename Traits::Arguments;
528
529 return []<size_t... I>(std::index_sequence<I...>, auto& inst) {
530 return Delegate<ReturnType(
531 std::tuple_element_t<I, Args>...)>::template From<Func>(inst);
532 }(std::make_index_sequence<std::tuple_size_v<Args>>{}, instance);
533}
534
535} // namespace helios
constexpr bool operator==(const Delegate &other) const noexcept
Compare two delegates for equality.
Definition delegate.hpp:237
constexpr void Reset() noexcept
Reset delegate to empty state.
Definition delegate.hpp:275
constexpr bool Valid() const noexcept
Check if delegate is bound to a callable.
Definition delegate.hpp:255
constexpr ReturnType Invoke(Args &&... args) const noexcept(std::is_nothrow_invocable_v< FunctionType, void *, Args &&... >)
Invoke delegate with exact argument types.
Definition delegate.hpp:281
ReturnType(*)(void *, Args...) FunctionType
Definition delegate.hpp:76
constexpr bool operator!=(const Delegate &other) const noexcept
Compare two delegates for inequality.
Definition delegate.hpp:247
constexpr void * InstancePtr() const noexcept
Get raw instance pointer stored inside delegate.
Definition delegate.hpp:265
static constexpr Delegate From() noexcept
Create delegate from a free function pointer.
constexpr Delegate() noexcept=default
Default constructs an empty delegate.
Type-erased callable wrapper for free and member functions.
Definition delegate.hpp:71
Concept to allow argument conversion with polymorphic relationships.
constexpr auto MakeDelegate() noexcept
Helper to create delegate from free function pointer.
Definition delegate.hpp:503
STL namespace.
Traits for member function pointers (non-const and const).
Definition delegate.hpp:35