Hi Geeks, Hope that you’re fine since the last post and doing well with your bugs and exceptions.
Le’ts get to the topic directly, I was implementing some dirty stuff using C++, and I had to implement a simple event handler, and suddenly some dirty thoughts jumped into my head and asked myself about implementing sort of generic Delegate.
If we were thinking about implementation of Delegate we have to worry about 2 things the first one the return type of the Delegate and the second is argument list, so we can make something like this (Basic Implementation)
#include <iostream> class Delegate { typedef void(*Callback)(void* p_ptrToObj, int parm); public: Delegate() {} Delegate(void* p_obj, Callback p_func) : m_ptrToObject(p_obj), m_ptrToFunction(p_func) {} template <class T, void (T::*TMethod)(int)> //function for making a delegate object static Delegate MakeDelegate(T* p_obj) { Delegate delegate(p_obj, &Call<T, TMethod>); return delegate; } void operator()(int p_param) { (*m_ptrToFunction)(m_ptrToObject, p_param); } private: Callback m_ptrToFunction; void *m_ptrToObject; template <class T, void (T::*TMethod)(int)> //Helper function for casting the untyped stored pointer (void*) static void Call(void* p_obj, int param) { T* ptr = static_cast<T*>(p_obj); return (ptr->*TMethod)(param); } }; //Testing Class class Test { public: void Test_foo(int val) { printf("Test_foo called with value =%d\n", val); } }; // Entry point void main(void) { Test testObj; Delegate delegate = Delegate::MakeDelegate < Test, &Test::Test_foo>(&testObj); delegate(13); }
ِAfter checking this implementation, we’ll find that it works with a specific function signature for the delegate function, simply a function that takes a parameter with type (int) and returns (void).
The second implementation is more generic than the previous one, C++ can support us to make a generic return types and argument types , let’s dirty our fingers with some code.
#include <iostream> template<typename TReturnType, typename TParam> class Delegate { typedef TReturnType(*Callback)(void* p_ptrToObj, TParam parm); public: Delegate() {} Delegate(void* p_obj, Callback p_func) : m_ptrToObject(p_obj), m_ptrToFunction(p_func) {} template <class T, TReturnType(T::*TMethod)(TParam)> //function for making a delegate object static Delegate MakeDelegate(T* ptrToObj) { Delegate delegate(ptrToObj, &Call<T, TMethod>); return delegate; } TReturnType operator()(TParam p_param) { return (*m_ptrToFunction)(m_ptrToObject, p_param); } private: Callback m_ptrToFunction; void *m_ptrToObject; template <class T, TReturnType(T::*TMethod)(TParam)> //Helper function for casting the untyped stored pointer (void*) static TReturnType Call(void* p_obj, TParam param) { T* ptr = static_cast<T*>(p_obj); return (ptr->*TMethod)(param); } }; //Testing Class class Test { public: int Test_foo(int val) { int res = val + 10; printf("Test_foo returned %d\n", res); return res; } }; // Entry point void main(void) { Test testObj; Delegate<int, int> delegate = Delegate<int, int> ::MakeDelegate<Test, &Test::Test_foo>(&testObj); delegate(20); }
Hmmm, as you can see, it’s little bit generic, but we still controlled with limited number of arguments, that’s not the target. we need to reach to dynamic return type and dynamic argument list.
Ok, I’ll tell you something about me, some days ago I was reading a book named “Modern C++ design”, seems old but golden one (قطعة من الجنة) and I was reading specifically the part related to Functors’ implementation, and I knew about something in C++ named TypeLists, you can read about it from the book and you can find its implementation here , and after little efforts of online searching I accidentally found something that may be a golden solution for my problem, it’s variadic templates.
variadic templates are templates that accept zero or more template arguments (non-types, types, templates) and supported in C++ since C++11, and it’s considered as a solution for a long standing C++ problem.
template<typename ... TParam>
I think the solution of our problem is clear now, and now it’s the time to dirty our hands with a real generic version of Delegates
#include <iostream> template<typename TReturnType, typename ... TParams> class Delegate { typedef TReturnType(*Callback)(void* p_ptrToObj, TParams...); public: Delegate() {} Delegate(void* p_obj, Callback p_func) : m_ptrToObject(p_obj), m_ptrToFunction(p_func) {} template <class T, TReturnType(T::*TMethod)(TParams...)> //function for making a delegate object static Delegate MakeDelegate(T* ptrToObj) { Delegate delegate(ptrToObj, &Call<T, TMethod>); return delegate; } TReturnType operator()(TParams...p_paramList) { return (*m_ptrToFunction)(m_ptrToObject, p_paramList...); } private: Callback m_ptrToFunction; void *m_ptrToObject; template <class T, TReturnType(T::*TMethod)(TParams...)> //Helper function for casting the untyped stored pointer (void*) static TReturnType Call(void* p_obj, TParams...p_paramList) { T* ptr = static_cast<T*>(p_obj); return (ptr->*TMethod)(p_paramList...); } }; //Testing Class class Test { public: int Test_foo(int val) { int res = val + 10; printf("Test_foo returned %d\n", res); return res; } int Test_bar(int x, int y, int z) { z = x*y; printf("Test_bar returned %d\n", z); return z; } }; // Entry point void main(void) { Test testObj; auto delegate = Delegate<int, int>::MakeDelegate<Test, &Test::Test_foo>(&testObj); delegate(20); auto delegate1 = Delegate<int, int,int,int>::MakeDelegate<Test, &Test::Test_bar>(&testObj); delegate1(10, 20, 30); }
Ohh, finally it’s done, finally we have Delegate implementation for different functions signatures.
A more handsome implementation here on github that supports both Multicast and Unicast delegates.
References:
- Modern C++ Design – Andrei Alexandrescu.
- Simple Delegates Implementation – Geek’s Blog.
- C++ Delegates on Steroids.
- Fast C++ Delegate – code project.
- Generic C++ Delegates Implementation.
- variadic tutorial.