Skip to content

Instantly share code, notes, and snippets.

@xenobrain
Last active December 31, 2025 21:24
Show Gist options
  • Select an option

  • Save xenobrain/ed90e8a54ad948cebf480fbbc3d0772b to your computer and use it in GitHub Desktop.

Select an option

Save xenobrain/ed90e8a54ad948cebf480fbbc3d0772b to your computer and use it in GitHub Desktop.
delegate
#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
// 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