I recently reached the point in a framework’s development where I required a solution for delegates. I could have turned to Don Clugston’s delegates on codeproject.com but I paused for thought. You see – while I do subscribe to the mentality of more typing for the sake of clarity, I must confess that I find C++’s template syntax unwieldy and wordy.
The thought I had was this; can you have a delegates solution in C++ that does not require any template syntax to use?
// Example delegate using "void MyObj::Method(int, int)" Delegate delegate = createDelegate(&myObj, &MyObj::Method); delegate(1, 2);
Well, you can. What follows is an explanation and some source code of how so.
Quick and simple
To begin, let’s consider what it is that makes Don’s delegates’ fast. It comes from both compliance with the C++ Standard and abuse of it. The Standard allows casting a method pointer to an unrelated method pointer via reinterpret_cast (5.2.10.6/9) and states that invoking this cast method however is to be considered undefined. In practice however the invocation actually does exactly what you would expect on leading compilers.
// Compliant, but the C++ Standard doesn't allow you to do anything with // the variable 'method'. typedef void (DestObj::*DestMethod)(int, int); DestMethod method = reinterpret_cast(&MyObj::Method); // Undefined but can work as expected provided underlying objects pointed // to by 'object' and 'method' match. DestObj* object = (DestObj*)myObjPtr; (object->*method)(1,2);
This means there is no longer a requirement for type info about the target method and the object it is invoked upon – both object and method pointers can be cast into variables of only loosely related types and invoked successfully (albeit illegally!). No longer is there a need for derived classes – the traditional pure-virtual “invoke” method. Additionally, invoking a delegate ends up being nearly as fast as conventional method call to boot.
The basis for a generic delegate container class is starting to take shape, but what about a method’s argument types?
Identifying the Arguments
While the requirement for a delegate template class has been eliminated there is still a need to identify the correct argument types. It would be impossible to ensure correct invocation of the delegate otherwise.
As the compiler can be used to automatically determine template parameters, a template factory function for the creation of fully-formed delegate objects can be used here. As it happens, Don’s delegates use this approach too.
template <typename T, typename A0, typename A1>
Delegate createDelegate<T* t, void (T::*m)(A0, A1)>
{
return Delegate(t, Delegate::Method(m), KeyRing::getKey<A0, A1>());
}
None of this crucial type info can be stored in the delegate class as it is cast away. This presents a hurdle as it is required to correctly invoke the target method pointer. Some manner of identifying template parameters is required.
Fortunately, if a compiler encounters a combination of parameters to a template class that it has previously specialised, this previous specialisation is reused. Any property that is unique per specialisation of said class can be used to match template parameters that have long since been forgotten. The pointer to a static method for example;
typedef void (*Key)();
template <typename A0=void, typename A1=void /*...An=void*/>
struct KeyRing
{
static Key getKey() { return reinterpret_cast<Key>(&KeyRing::getKey); }
};
Invocation
All that remains is to handle the invoking of the delegate using templated and overloaded function call operator. The automatically-deduced template parameters play their part here again, allowing for both an argument-key check and the appropriate casting of the method pointer so it can be invoked in the expected manner.
struct Delegate
{
typedef void (Delegate::*Method)();
Key m_key;
Delegate* m_object;
Method m_castMethod;
/*
void operator () ();
template <typename A0> void operator () (A0);
template <typename A0, ...typename An> void operator () (A0, ...An);
*/
template <typename A0, typename A1> void operator () (A0, A1)
{
assert(typeId<A0, A1>::getKey() == m_key);
// cast m_method to "void Delegate::*(A0, A1)" and invoke.
}
};
The Conclusion
So it turns out you can have delegates without having to litter template syntax all over the shop. It is trivial to add transparent support free functions and they can be respectably fast too.
However, the practicality of the result is questionable. As there are some significant gotchas, perhaps judicial use of the typedef keyword is more appropriate if you wish to reduce template syntax use? The catches are (in no particular order);
- Some factors have not been considered at all! Namely; calling convention and methods declared with qualifiers such as const.
- They are not self-documenting. Template parameters may well be unwieldy but the expected method/function prototype is clear and this is a valuable property. A derived template class could be used here to aid clarity in the source code.
- It’s dangerous. There is a high error risk as type info is not set until method is assigned to a delegate object. Again, a templated derived class could be used to prime delegate keys runtime-checks are carried out on assignment.
- Runtime-only checking. It is not possible to detect argument mismatches at compile time and without sufficient QA this could leave dormant issues in wait.
- Implicit type conversion is lost but it could be emulated with some template foo.
- Void-only return type. Supporting delegates that can return a value is tricky – it is actually easy to get return type info, but the automatic template instantiation doesn’t work so well when a method’s return type is templated. References can be used as a workaround (EDIT: No they can’t. Getting values back is next to impossible without jumping through hoops, effectively collaborating the questionable practicality).
- Not compatible with shared libraries. As is traditionally the case with templates, shared libraries (such as DLLs) will cause trouble as keys are most likely to differ between projects.
The Source Code
Download the source code (zip).
Here is the source code to accompany this post (zlib license). Support for free functions is included and some of the suggestions discussed in the conclusion are implemented. Compiles quite happily with MSVC 2005, 2008 (32bit), gcc (Ubuntu, 64bit), and maybe others too!
Update 2011-03-28: Removed incorrect attribution of Koenig lookup to compiler automatically deducing template parameters. Thanks Alexander!












Why don’t you use Boost.Function?
Two reasons in this particular instance. This was more of an experiment to see if the amount of required template syntax could be reduced. As I concluded, it can be eliminated entirely but it probably shouldn’t. This is why the sample code introduces a derived template class. I’m certain there’s a way to get it to conform to the C++ Standard too.
Secondly, I didn’t want to introduce a dependency on Boost. Boost is not always the answer and is often not a welcome dependency. I’ve worked on plenty of code bases where Boost is not an option so other solutions are required.
Hi Martin,
There is no such thing as “dependency on Boost” as Boost is the set of libraries, and not the library itself.
In this case you can only speak about dependency on Boost.Function.
If you bother that for using Boost.Function you need to install all other libraries, than you should use bcp tool for extracting exactly what you need: http://www.boost.org/doc/libs/1_46_0/tools/bcp/doc/html/index.html
Also I would note that deducing template arguments for function template has a little to do with ADL (“Koenig lookup”).
Hello Alexander,
Thanks for noting my misappropriation of the ADL term! I’ll amend the post appropriately when I have a moment.
I was unaware of “bcp” – it’s a shame that Boost doesn’t publicise it more clearly, instead directing people to a 40-80Mb archive of everything (and hence where I get my “dependency on Boost” from).
I tried “bcp” out for “function” and “bind” and it dropped ~8.0Mb of headers across 10+ individual Boost libraries. That quite surprised me – I consider that to be rather a lot for a simple delegate solution.
Martin.