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