Update: A full source article and a more detailed discussion are published in the Code project.
Well, the problem with method pointers is that they are not all the same size. Therefore, instead of directly storing pointers to methods, we need to "standardize" them so that they have a constant size. This is what Don Clugston is trying to do in his Draft Codex article. He does this using the intimate knowledge of the most popular compilers. I argue that this can be done in “normal” C ++ without requiring such knowledge.
Consider the following code:
void DoSomething(int) { } void InvokeCallback(void (*callback)(int)) { callback(42); } int main() { InvokeCallback(&DoSomething); return 0; }
This is one way to implement a callback using the plain old function pointer. However, this does not work for methods in objects. Let fix it:
class Foo { public: void DoSomething(int) {} static void DoSomethingWrapper(void* obj, int param) { static_cast<Foo*>(obj)->DoSomething(param); } }; void InvokeCallback(void* instance, void (*callback)(void*, int)) { callback(instance, 42); } int main() { Foo f; InvokeCallback(static_cast<void*>(&f), &Foo::DoSomethingWrapper); return 0; }
We now have a callback system that can work for both free and member functions. This, however, is inconvenient and error prone. However, there is a pattern - using a wrapper function to "forward" a call to a static function to a method call in the corresponding instance. We can use templates to help with this - try generalizing the wrapper function:
template<typename R, class T, typename A1, R (T::*Func)(A1)> R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } class Foo { public: void DoSomething(int) {} }; void InvokeCallback(void* instance, void (*callback)(void*, int)) { callback(instance, 42); } int main() { Foo f; InvokeCallback(static_cast<void*>(&f), &Wrapper<void, Foo, int, &Foo::DoSomething> ); return 0; }
This is still extremely awkward, but at least for now we don’t need to write a wrapper function every time (at least for argument 1 of the argument). Another thing that we can generalize is that we always pass a pointer to void* . Instead of passing it as two different meanings, why not put them together? And while we are doing this, why not generalize it? Hey, let me insert operator()() so we can name it as a function!
template<typename R, typename A1> class Callback { public: typedef R (*FuncType)(void*, A1); Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } private: void* obj; FuncType func; }; template<typename R, class T, typename A1, R (T::*Func)(A1)> R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } class Foo { public: void DoSomething(int) {} }; void InvokeCallback(Callback<void, int> callback) { callback(42); } int main() { Foo f; Callback<void, int> cb(static_cast<void*>(&f), &Wrapper<void, Foo, int, &Foo::DoSomething>); InvokeCallback(cb); return 0; }
We are making progress! But now our problem is that the syntax is absolutely terrible. The syntax seems redundant; can the compiler determine the types from the pointer to the method itself? Unfortunately not, but we can help him. Remember that the compiler can infer types through the output of a template argument in a function call. So why don't we start with this?
template<typename R, class T, typename A1> void DeduceMemCallback(R (T::*)(A1)) {}
Inside the function, we know what R , T and A1 . So what if we can build a structure that can “hold” these types and return them from a function?
template<typename R, class T, typename A1> struct DeduceMemCallbackTag { }; template<typename R, class T, typename A1> DeduceMemCallbackTag2<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); }
And since DeduceMemCallbackTag knows about types, why not put our wrapper function as a static function in it? And since it has a wrapper function, why not put the code to create our Callback object in it?
template<typename R, typename A1> class Callback { public: typedef R (*FuncType)(void*, A1); Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } private: void* obj; FuncType func; }; template<typename R, class T, typename A1> struct DeduceMemCallbackTag { template<R (T::*Func)(A1)> static R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } template<R (T::*Func)(A1)> inline static Callback<R, A1> Bind(T* o) { return Callback<R, A1>(o, &DeduceMemCallbackTag::Wrapper<Func>); } }; template<typename R, class T, typename A1> DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); }
The C ++ standard allows you to call static functions on instances (!):
class Foo { public: void DoSomething(int) {} }; void InvokeCallback(Callback<void, int> callback) { callback(42); } int main() { Foo f; InvokeCallback( DeduceMemCallback(&Foo::DoSomething) .Bind<&Foo::DoSomething>(&f) ); return 0; }
However, this is a long expression, but we can put this in a simple macro (!):
template<typename R, typename A1> class Callback { public: typedef R (*FuncType)(void*, A1); Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } private: void* obj; FuncType func; }; template<typename R, class T, typename A1> struct DeduceMemCallbackTag { template<R (T::*Func)(A1)> static R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } template<R (T::*Func)(A1)> inline static Callback<R, A1> Bind(T* o) { return Callback<R, A1>(o, &DeduceMemCallbackTag::Wrapper<Func>); } }; template<typename R, class T, typename A1> DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); } #define BIND_MEM_CB(memFuncPtr, instancePtr) \ (DeduceMemCallback(memFuncPtr).Bind<(memFuncPtr)>(instancePtr)) class Foo { public: void DoSomething(int) {} }; void InvokeCallback(Callback<void, int> callback) { callback(42); } int main() { Foo f; InvokeCallback(BIND_MEM_CB(&Foo::DoSomething, &f)); return 0; }
We can improve the Callback object by adding a secure bool. It’s also nice to turn off equality operators, since you cannot compare two Callback objects. Even better, use partial specialization to allow "preferred syntax". This gives us:
template<typename FuncSignature> class Callback; template<typename R, typename A1> class Callback<R (A1)> { public: typedef R (*FuncType)(void*, A1); Callback() : obj(0), func(0) {} Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } typedef void* Callback::*SafeBoolType; operator SafeBoolType() const { return func != 0? &Callback::obj : 0; } bool operator!() const { return func == 0; } private: void* obj; FuncType func; }; template<typename R, typename A1>
Usage example:
class Foo { public: float DoSomething(int n) { return n / 100.0f; } }; float InvokeCallback(int n, Callback<float (int)> callback) { if(callback) { return callback(n); } return 0.0f; } int main() { Foo f; float result = InvokeCallback(97, BIND_MEM_CB(&Foo::DoSomething, &f));
I tested this on the Visual C ++ compiler (version 15.00.30729.01, the one that comes with VS 2008), and you need a fairly recent compiler to use the code. When checking for disassembly, the compiler was able to optimize the wrapper function and call DeduceMemCallback , reducing it to simple pointer assignments.
It is easy to use for both sides of the callback and uses only (what I consider) standard C ++. The code shown above works for member functions with 1 argument, but can be generalized to more arguments. It can also be generalized by allowing support for static functions.
Note that the Callback object does not require heap allocation - they are of constant size due to this “standardization” procedure. Because of this, it is possible that the Callback object will be a member of a larger class, since it has a default constructor. It can also be assigned (copy generation functions created by the compiler are sufficient). This is also typical, thanks to the patterns.