Last active
December 31, 2025 21:24
-
-
Save xenobrain/ed90e8a54ad948cebf480fbbc3d0772b to your computer and use it in GitHub Desktop.
delegate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef DELEGATE_H | |
| #define DELEGATE_H | |
| #if defined(_MSC_VER) | |
| #define DEBUG_BREAK __debugbreak() | |
| #else | |
| #include <csignal> | |
| #ifdef __EMSCRIPTEN__ | |
| #include <emscripten.h> | |
| #define DEBUG_BREAK EM_ASM({ debugger; }) | |
| #else | |
| #define DEBUG_BREAK raise(SIGTRAP) | |
| #endif | |
| #endif | |
| template<class T> struct IsVoid { static constexpr bool value = false; }; | |
| template<> struct IsVoid<void> { static constexpr bool value = true; }; | |
| template<> struct IsVoid<void const> { static constexpr bool value = true; }; | |
| template<> struct IsVoid<void volatile> { static constexpr bool value = true; }; | |
| template<> struct IsVoid<void const volatile> { static constexpr bool value = true; }; | |
| template<class T> struct RemoveConst { using type = T; }; | |
| template<class T> struct RemoveConst<T const> { using type = T; }; | |
| template<class T> struct RemoveVolatile { using type = T; }; | |
| template<class T> struct RemoveVolatile<T volatile> { using type = T; }; | |
| template<class T> struct RemoveCV { using type = RemoveVolatile<typename RemoveConst<T>::type>::type; }; | |
| template<class T> struct RemoveRef { using type = T; }; | |
| template<class T> struct RemoveRef<T&> { using type = T; }; | |
| template<class T> struct RemoveRef<T&&> { using type = T; }; | |
| template<typename T> using Decay = RemoveCV<typename RemoveRef<T>::type>::type; | |
| template <typename F, typename... Args> concept Invokable = requires(F f, Args... args) { { f(args...) }; }; | |
| template <typename F, typename... Args> struct IsInvokable { static constexpr bool value = Invokable<F, Args...>; }; | |
| template <typename T> class Delegate; | |
| template <typename R, typename... Args> class Delegate<R(Args...)> { | |
| public: | |
| Delegate() = default; | |
| template<typename F> explicit Delegate(F&& f) requires IsInvokable<R,F,Args...>::value { bind(static_cast<F&&>(f)); } | |
| template <R(*function)(Args...)> auto bind() -> Delegate& { | |
| reset(); | |
| instance_ = nullptr; | |
| proxy_ = &function_proxy<function>; | |
| deleter_ = nullptr; | |
| return *this; | |
| } | |
| template <class C, R(C::* function)(Args...)> auto bind(C* instance) -> Delegate& { | |
| reset(); | |
| instance_ = instance; | |
| proxy_ = &method_proxy<C,function>; | |
| deleter_ = nullptr; | |
| return *this; | |
| } | |
| template <class C, R(C::*function)(Args...) const> auto bind(const C* instance) -> Delegate& { | |
| reset(); | |
| instance_ = const_cast<C*>(instance); | |
| proxy_ = &const_method_proxy<C,function>; | |
| deleter_ = nullptr; | |
| return *this; | |
| } | |
| template <typename Lambda> auto bind(Lambda&& lambda) -> Delegate& { | |
| reset(); | |
| using Decayed = Decay<Lambda>; | |
| // In-place storage only if size fits and alignment fits | |
| if constexpr (sizeof(Decayed) <= STORAGE_SIZE && alignof(Decayed) <= alignof(decltype(storage_))) { | |
| new (&storage_) Decayed(static_cast<Lambda&&>(lambda)); | |
| instance_ = &storage_; | |
| deleter_ = [](void* inst) { static_cast<Decayed*>(inst)->~Decayed(); }; | |
| } else { | |
| // Heap allocation | |
| instance_ = new Decayed(static_cast<Lambda&&>(lambda)); | |
| deleter_ = [](void* inst) { delete static_cast<Decayed*>(inst); }; | |
| } | |
| proxy_ = &lambda_trampoline<Decayed>; | |
| return *this; | |
| } | |
| auto invoke(Args... args) const -> R { | |
| if (proxy_) { | |
| if constexpr (IsVoid<R>::value) { | |
| proxy_(instance_, static_cast<Args&&>(args)...); | |
| return; | |
| } else { | |
| return proxy_(instance_, static_cast<Args&&>(args)...); | |
| } | |
| } | |
| XC_DEBUG_BREAK; | |
| if constexpr (IsVoid<R>::value) return; | |
| else return R(); | |
| } | |
| template <typename... CallArgs> auto operator()(CallArgs&&... args) const -> R { | |
| return invoke(static_cast<CallArgs&&>(args)...); | |
| } | |
| explicit operator bool() const { return proxy_ != nullptr; } | |
| private: | |
| static constexpr usize STORAGE_SIZE{ 3u * sizeof(void*) }; | |
| typedef R(*proxy_function)(void*, Args...); | |
| template <R(*function)(Args...)> auto static function_proxy(void*, Args... args) -> R { | |
| return function(static_cast<Args&&>(args)...); | |
| } | |
| template <class C, R(C::* function)(Args...)> auto static method_proxy(void* instance, Args... args) -> R { | |
| return (static_cast<C*>(instance)->*function)(static_cast<Args&&>(args)...); | |
| } | |
| template <class C, R(C::* function)(Args...) const> auto static const_method_proxy(void* instance, Args... args) -> R { | |
| return (static_cast<const C*>(instance)->*function)(static_cast<Args&&>(args)...); | |
| } | |
| template <typename L> static R lambda_trampoline(void* inst, Args... args) { | |
| return (*static_cast<L*>(inst))(static_cast<Args&&>(args)...); | |
| } | |
| void reset() { | |
| if (deleter_ && instance_) { deleter_(instance_); } | |
| instance_ = nullptr; | |
| proxy_ = nullptr; | |
| deleter_ = nullptr; | |
| } | |
| void* instance_{}; | |
| proxy_function proxy_{}; | |
| void (*deleter_)(void*) = nullptr; | |
| alignas(max_align) char storage_[STORAGE_SIZE]{}; | |
| }; | |
| #endif // DELEGATE_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // pre-C++11 version | |
| //#include "Delegate.h" | |
| // C++11 version | |
| #include "delegate.h" | |
| #include <windows.h> | |
| void Func0(void) | |
| { | |
| } | |
| int Func1(int a) | |
| { | |
| return a + 10; | |
| } | |
| float Func2(float a, float b) | |
| { | |
| return a + b; | |
| } | |
| struct Test | |
| { | |
| void Func0(void) | |
| { | |
| } | |
| void Func0(void) const | |
| { | |
| } | |
| int Func1(int a) | |
| { | |
| return a + 20; | |
| } | |
| int Func1(int a) const | |
| { | |
| return a + 20; | |
| } | |
| float Func2(float a, float b) | |
| { | |
| return a + b; | |
| } | |
| float Func2(float a, float b) const | |
| { | |
| return a + b; | |
| } | |
| }; | |
| int main(void) | |
| { | |
| // delegate with zero arguments | |
| { | |
| typedef delegate<void(void)> TestDelegate; | |
| TestDelegate d; | |
| d.bind<&Func0>(); | |
| d.invoke(); | |
| } | |
| // delegate with one argument | |
| { | |
| typedef delegate<int(int)> TestDelegate; | |
| TestDelegate d; | |
| d.bind<&Func1>(); | |
| d.invoke(10); | |
| Test t; | |
| d.bind<Test, &Test::Func1>(&t); | |
| d.invoke(10); | |
| const Test ct; | |
| d.bind<Test, &Test::Func1>(&ct); | |
| d.invoke(10); | |
| } | |
| // delegate with two arguments | |
| { | |
| typedef delegate<float(float, float)> TestDelegate; | |
| TestDelegate d; | |
| d.bind<&Func2>(); | |
| d.invoke(10.0f, 20.0f); | |
| Test t; | |
| d.bind<Test, &Test::Func2>(&t); | |
| d.invoke(10.0f, 20.0f); | |
| const Test ct; | |
| d.bind<Test, &Test::Func2>(&ct); | |
| d.invoke(10.0f, 20.0f); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment