The Curious Case of the Compile-Time Function

A Crime Has Been Committed

18 months ago I described a version of my Event/Callback library in an Overload article [Bass]. This library is used extensively in my employer’s control systems software. A typical use looks like this:

// A class of objects that monitor some event.
class Observer
{
public:
    Observer(Event& event)
      : callback(bind_1st(memfun(&Observer::handler), this))
      , connection(event, &callback)
    {}

private:
    void handler(int); // the event handler

    typedef Callback::Adapter::type Callback_Type;

    Callback_Type callback; // a function object
    Callback::Connection connection; // event  callback
};

Exhibit 1: The event/callback library in action.

The key feature in this example is that a callback and an event/callback connection are both stored in the Observer as data members. Some attempt has been made to support this idiom by providing various helpers (the bind_1st() and memfun() function templates[1] and the Callback::Adapter class template). However, there is still quite a lot of rather verbose boilerplate code. And that’s a crime.

It has been clear for some time that we should be able to improve on this. There seems to be no fundamental reason, for example, why we can’t combine the callback and its connection into a single class template (Bound_Callback, say) and use it like this:

// A class of objects that monitor some event.
class Observer
{
public:
    Observer(Event& event)
      : callback(event, &Observer::handler, this)
    {}

private:
    void handler(int); // the event handler

    Bound_Callback callback;
};

Exhibit 2: The goal.

The question is how should we write the Bound_Callback template?

Suspects and Red Herrings

The first thing that comes to mind is Boost [boost]. There’s bound to be a Boost library that provides what we need. The trouble is I can’t find one.

Boost.Bind provides a lovely family of bind() functions that generate all kinds of function objects. Unfortunately, their return types are unspecified, so we can’t declare data members of those types.

Then there’s Boost.Function, which was designed for a very similar job and does provide types we can use as data members. I believe we could, in fact, use the boost::function<> template as the callback part of our Bound_Callback What I haven’t told you, though, is that an Event can only be connected to callbacks derived from Callback::Function. Clearly, as boost::function<> isn’t derived from this base class it doesn’t provide everything we need. And, of course, it doesn’t know how to make the event/callback connection, either.

So, what about Boost.Signals? Well, yes, we could replace the whole of our event/callback library with boost::signals, but I’m reluctant to do that for several (not very good) reasons. First of all, I don’t like the names: “signal” is already used for something else in Unix operating systems, and “slot” is a truly bizarre word for a callback function. Secondly, Boost.Signals does more than we need or want. Specifically, I’m not convinced that a general-purpose event/callback library should do its own object lifetime management and, anyway, we couldn’t use that feature in common cases like Exhibit 1. Finally, if we were to use Boost.Signals the crime would be reduced to a misdemeanour and there would be little or no motivation for this article!

A Promising Lead

The astute reader may have spotted a clue in the first exhibit. The typedef isn’t there just to provide a reasonably short name for the callback type – it also shows a template meta-function in action.

A meta-function in C++ is a compile-time analogue of an ordinary (run-time) function. Well-behaved run-time functions perform an operation on a set of values supplied as parameters and generate a new value as their result. Meta-functions typically perform an operation on a set of types supplied as parameters and generate a new type as their result.

In its simplest form, a meta-function taking a single type parameter and returning another type as its result looks like this:

template
struct meta_function
{
    typedef  type;
};

Exhibit 3: A simple meta-function.

In C++, a meta-function always involves a template. The metafunction’s parameters are the template’s parameters and the metafunction’s result is a nested type name or integral constant. The Boost Meta-Programming Library adopts the convention that a meta-function’s result is called type (if it’s a type) or value (if it’s an integral constant) and that same convention is used here.

Now, suppose we had a meta-function that takes a pointer-to-member-function type and returns the function’s parameter type.

template // Result (Class::*Pmf)(Arg)
struct argument
{
    typedef  type; // type == Arg
};

Exhibit 4: A magical meta-function.

Similarly, we can imagine meta-functions that extract from a pointer-to-member-function the function’s result type and the class of which the function is a member. We could now write a Bound_Callback template along the lines of Exhibit 5.

// A callback bound to an event.
template
class Bound_Callback
    : public Callback::Function::type>
{
public:
    typedef typename argument::type Arg;
    typedef typename result::type Result;
    typedef typename class_::type Class;

    Bound_Callback(Event& event, Pmf f, Class* p)
      : pointer(p), function(f) , connection(event, this)
    {}

    Result operator()(Arg value)
    {
        return (pointer->*function)(value);
    }

private:
    Class* pointer;
    Pmf function;
    Callback::Connection connection;
};

Exhibit 5: Using a meta-function.

This would be exactly what we need to implement the sort of class illustrated in Exhibit 2. As Sherlock Holmes himself might say, “Well done, Watson. Now, how can we implement the argument, result and class_ metafunctions?”

Reviewing the Evidence

The argument meta-function shown in Exhibit 4 works perfectly, but only if your name is Harry Potter. Plodding detectives (and C++ compilers) can’t be expected to perform magic. I was puzzled. Then I spotted something odd among the evidence:

template
struct argument
{
    typedef Arg type;
};

Exhibit 6: A meta-function for clairvoyants.

Here’s a meta-function that extracts the parameter type without using magic. It just needs a little clairvoyance. If you know in advance what the parameter type is you can use this metafunction to generate the type you need. The heroic sleuth in detective novels may seem to be clairvoyant at times but programmers are not that clever (not even pizza-stuffed, caffeine-soaked real programmers).

My search for the argument meta-function had run up a blind alley. It was late. I was tired. I was getting desperate. And then it hit me. We were looking for a meta-function with one parameter (like the magical one), but to implement it we need three parameters (like the one for clairvoyants). We need a specialisation.

// Declaration of general template
template struct argument;

// Partial specialisation for pointers to member functions
template
struct argument
{
    typedef Arg type;
};

Exhibit 7: Extracting the parameter type.

The specialisation tells the compiler how to instantiate argument when Pmf is a pointer to a member function of any class, taking a single parameter of any type and returning a result of any type.

The same technique works for the result and class_ meta-functions, too. In each case, the general template takes one parameter, but the specialisation takes three. The compiler performs a form of pattern matching to break down a single pointer-to-member-function type into its three components. For example:

typedef result::type Result;

Result* null_pointer = 0; // Result is void

Exhibit 8: Using the resultmeta-function.

When it sees the result template being used the compiler compares the template argument (pointer-to-member-of-Observer) with the template parameter of the specialisation (any pointer-to-member- function). In this case the argument matches the parameter and the compiler deduces Result = void, Class = Observer, Arg = int. The compiler then instantiates the specialisation which defines result::type as void.

The Case is Closed

So that’s it. The crime is solved. All that’s left is to prepare a case for presentation in court and let justice take its course. I’ve had enough for one day. “I’m off to the pub, anyone want to join me?”, I called across the office.

“Well, that was the usual warm, friendly response”, I thought, as I sat on my own with a pint. “No thanks”, “Sorry, can’t”, “Too busy” they said. But something was still bothering me. Does Bound_Callback still work if we try to connect a handler function taking an int to an Event that publishes a short? And what if we need to connect an Event to something other than a member function – like a non-member function or a function object?

These thoughts were still churning over in my mind when, sometime after midnight, I tumbled into bed and soon fell into a fitful sleep.

References

[Bass] Phil Bass, “Implementing the Observer Pattern in C++”, Overload 53, February 2003.

[boost] See www.boost.org


[1] These are not-quite-standard variations of std::bind1st() and std::mem_fun() developed in-house for reasons that are not important here.

First published in Overload 62, August 2004

By stoneyfish

Humanist and retired software engineer with a love of music.

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: