Last active
June 16, 2026 22:35
-
-
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
| module; | |
| #include <xc.foundation/defines.h> | |
| export module xc.foundation.delegate.api; | |
| import xc.foundation.types; | |
| export namespace xc { | |
| template <typename F> | |
| class Delegate; | |
| template <typename R, typename... Args> | |
| class Delegate<R(Args...)> { | |
| using proxy_fn = R (*)(void*, Args...); | |
| public: | |
| // bind free function | |
| template <R (*Fn)(Args...)> | |
| auto bind() noexcept -> Delegate& { | |
| instance_ = nullptr; | |
| proxy_ = &fn_proxy<Fn>; | |
| return *this; | |
| } | |
| // bind non-const member function | |
| template <class C, R (C::*Fn)(Args...)> | |
| auto bind(C* inst) noexcept -> Delegate& { | |
| instance_ = inst; | |
| proxy_ = &method_proxy<C, Fn>; | |
| return *this; | |
| } | |
| // bind const member function | |
| template <class C, R (C::*Fn)(Args...) const> | |
| auto bind(C const* inst) noexcept -> Delegate& { | |
| instance_ = const_cast<C*>(inst); | |
| proxy_ = &const_method_proxy<C, Fn>; | |
| return *this; | |
| } | |
| XC_FORCE_INLINE auto invoke(Args... args) const -> R { | |
| XC_ASSERT(proxy_ != nullptr); | |
| if constexpr (is_void_v<R>) { | |
| proxy_(instance_, static_cast<Args&&>(args)...); | |
| } | |
| else { | |
| return proxy_(instance_, static_cast<Args&&>(args)...); | |
| } | |
| } | |
| XC_FORCE_INLINE auto operator()(Args... args) const -> R { return invoke(static_cast<Args&&>(args)...); } | |
| explicit operator bool() const noexcept { return proxy_ != nullptr; } | |
| auto reset() noexcept -> void { | |
| instance_ = nullptr; | |
| proxy_ = nullptr; | |
| } | |
| private: | |
| template <R (*Fn)(Args...)> | |
| static auto fn_proxy(void*, Args... args) noexcept -> R { | |
| return Fn(static_cast<Args&&>(args)...); | |
| } | |
| template <class C, R (C::*Fn)(Args...)> | |
| static auto method_proxy(void* inst, Args... args) noexcept -> R { | |
| return (static_cast<C*>(inst)->*Fn)(static_cast<Args&&>(args)...); | |
| } | |
| template <class C, R (C::*Fn)(Args...) const> | |
| static auto const_method_proxy(void* inst, Args... args) noexcept -> R { | |
| return (static_cast<C const*>(inst)->*Fn)(static_cast<Args&&>(args)...); | |
| } | |
| void* instance_{}; | |
| proxy_fn proxy_{}; | |
| }; | |
| static_assert(is_trivially_copyable_v<Delegate<i32()>>); | |
| static_assert(sizeof(Delegate<i32()>) == 2 * sizeof(void*)); | |
| } |
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
| #include <xc.foundation/defines.h> | |
| import xc.foundation.types; | |
| import xc.foundation.delegate.api; | |
| // Compile-time guarantees | |
| namespace compile_time { | |
| using D = xc::Delegate<xc::i32()>; | |
| static_assert(xc::is_trivially_copyable_v<D>, "Delegate must be trivially copyable"); | |
| static_assert(sizeof(D) == 2 * sizeof(void*), "Delegate must be exactly two pointers (16 B on 64-bit)"); | |
| static_assert(xc::is_void_v<void>); | |
| inline auto run() -> void { /* static_asserts fire at compile time */ } | |
| } | |
| // Free-function bind test | |
| namespace free_fn { | |
| inline xc::i32 counter{}; | |
| extern "C" xc::i32 add(xc::i32 a, xc::i32 b) { | |
| return a + b; | |
| } | |
| auto run() -> void { | |
| xc::Delegate<xc::i32(xc::i32, xc::i32)> d; | |
| XC_ASSERT(!d); | |
| d.bind<add>(); | |
| XC_ASSERT(d); | |
| XC_ASSERT(d(3, 4) == 7); | |
| XC_ASSERT(d.invoke(-1, 10) == 9); | |
| // Copy: trivial memcpy semantics | |
| auto d2 = d; | |
| XC_ASSERT(d2(100, 200) == 300); | |
| d.reset(); | |
| XC_ASSERT(!d); | |
| } | |
| } | |
| // Member-function bind test | |
| namespace member_fn { | |
| struct Calculator { | |
| xc::i32 base{}; | |
| auto multiply(xc::i32 x) -> xc::i32 { return base * x; } | |
| }; | |
| auto run() -> void { | |
| Calculator calc{42}; | |
| xc::Delegate<xc::i32(xc::i32)> d; | |
| d.bind<Calculator, &Calculator::multiply>(&calc); | |
| XC_ASSERT(d(3) == 126); | |
| calc.base = 10; | |
| XC_ASSERT(d(7) == 70); // live pointer to instance | |
| // Copy preserves the same instance pointer | |
| auto d2 = d; | |
| XC_ASSERT(d2(5) == 50); | |
| } | |
| } | |
| // Const member-function bind test | |
| namespace const_fn { | |
| struct Sensor { | |
| xc::f32 value{}; | |
| auto read() const -> xc::f32 { return value; } | |
| }; | |
| auto run() -> void { | |
| Sensor s{3.14f}; | |
| xc::Delegate<xc::f32()> d; | |
| d.bind<Sensor, &Sensor::read>(&s); | |
| XC_ASSERT(d() == 3.14f); | |
| s.value = 2.71f; | |
| XC_ASSERT(d() == 2.71f); | |
| } | |
| } | |
| // Void-return delegate test | |
| namespace void_ret { | |
| inline bool called{}; | |
| extern "C" void set_flag() { | |
| called = true; | |
| } | |
| auto run() -> void { | |
| called = false; | |
| xc::Delegate<void()> d; | |
| d.bind<set_flag>(); | |
| d(); | |
| XC_ASSERT(called); | |
| } | |
| } | |
| //Entry point | |
| extern "C" auto entry() -> void { | |
| compile_time::run(); // NOLINT — body is empty; static_asserts fire at compile time | |
| free_fn::run(); | |
| member_fn::run(); | |
| const_fn::run(); | |
| void_ret::run(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment