Using C++ Member Functions for C Function Pointers

The research department here at p-nand-q.com is pleased to announce another totally usefull study.... not!

Understanding the problem

The consensus on the internet is that you cannot pass C++ member function pointers to functions expecting C function pointers.

For example, a typical question is How to pass a method to qsort?, and the answer marked as "correct" says you should provide the address of a static function. Clearly that is too easy: static class member functions are mostly the same as plain old C functions, so in a way that answer is simply cheating... Our computer scientists (well, there is only one of them, and he isn't a really good scientist either) came up with an ingenious way for programmers to transcend classical logic and pass member functions.

Using C Function Pointers as C Function Pointers

Let's work this through by using a time-honoured C function: qsort.

Assume you have some infrastructure that allows you to write the following:

    std::vector<int> test;
    
    // create an array of 100 ints with random data
    SetupTestVectorWithRandomData(test, 100);

If you want to sort that list with the C qsort function, you would traditionally have a C function and use it like the following:

int PlainOldCCompareFunc(const void* a, const void* b)
{
    return *static_cast<const int*>(a) - *static_cast<const int*>(b);
}
...
// sort array
qsort( &test[0], test.size(), sizeof(int), &PlainOldCCompareFunc);

// verify it is indeed sorted
assert( IsSortedAscending(test) );

OK, so far this is the classical solution. But you're using a C function, and there is a rule in your contract that C functions get paid less than C++ functions, so you need to be seen passing a C++ method instead. What do you do?

Using Pointers to Static C++ Methods as C Function Pointers

Well, you declare the method as static. Example:

class ClassWithStaticCallback
{
public:
    static int StaticMemberFunction(const void* a, const void* b)
    {
        return *static_cast<const int*>(a) - *static_cast<const int*>(b);
    }
};
...
// sort array
qsort( &test[0], test.size(), sizeof(int), 
    &ClassWithStaticCallback::StaticMemberFunction);

// verify it is indeed sorted
assert( IsSortedAscending(test) );

Well, this is the standard, boring answer you'd get if you asked regular people.

Using C++ Methods as C Function Pointers

But you're unsatisfied with that solution. It is too easy. It is too readable. It doesn't help you in securing a good job. You need more. And we are here to give it to you. We will show you how you can finally write code like this:

class ClassWithCallback
{
public:
    ClassWithCallback(bool bSortAscending)
        :   m_bSortAscending( bSortAscending )
    {
    }

    /* Look ma, no static! */
    int CompareMemberFunc(const void* a, const void* b)
    {
        // access to member variables ...
        if( m_bSortAscending )
        {
            return *static_cast<const int*>(a) - *static_cast<const int*>(b);
        }
        else
        {
            return *static_cast<const int*>(b) - *static_cast<const int*>(a);
        }
    }
private:
    bool m_bSortAscending;
};
...
ClassWithCallback sortAscending(true), sortDescending(false);

// sort array ascending
qsort( &test[0], test.size(), sizeof(int), 
    QSortMemberFunctionCallback(&sortAscending, &ClassWithCallback::CompareMemberFunc));

// verify it is indeed sorted
assert( IsSortedAscending(test) );

// sort array descending
qsort( &test[0], test.size(), sizeof(int), 
    QSortMemberFunctionCallback(&sortDescending, &ClassWithCallback::CompareMemberFunc));

// verify it is indeed sorted
assert( IsSortedDescending(test) );

That is more like it. So how does it work?

How it works

Let's start by doing a short recap of what we know. Take a look at the following declaration:

typedef int (*LPFN_QSortCCallback)(const void* a, const void* b);
typedef int (ClassWithCallback::*LPFN_QSortMemberFunctionCallback)(const void* a, const void* b);

So, a C function pointer is a different type than a C++ member function pointer. When the compiler sees a C++ member function like this:

int ClassWithCallback::CompareMemberFunc(const void* a, const void* b)

what it really does is create a C function like this:

int CompareMemberFunc(ClassWithCallback* this, const void* a, const void* b)

It creates a hidden "this-pointer". This immediately explains why the two function pointers are not compatible: one points to a function receiving two args, the other to a function receiving three args. To put it another way: the caller of the function pointer will provide only two arguments, not three. (By the way: this also explains why static functions work: they have no object associated, so there is no hidden this-pointer generated, so their syntax looks the same). So what we need to do is this

To put it yet another way: somehow, our function needs state.

Now, at this point an obvious solution can be seen: use global variables. Example:

class ClassWithCallback
{
    ...
    int CompareMemberFunc(const void* a, const void* b)
    {
        ...
    }
    
    static int C_Compatible_StaticFunc(const void* a, const void* b)
    {
        // somehow, somewhere there is a global ClassWithCallback pointer:
        return g_pClassWithCallback->CompareMemberFunc(a, b);
    }
};

However, this has a couple of problems:

Enter templates. The basic idea is this: each template instantiation will create a different set of functions, including a different set of static functions.

At the core of our solution is a template very much like this:

template <int context> class DynamicQSortCallback 
{
public:
    static int GeneratedStaticFunction(const void* a, const void* b)
    {
        return StaticInvoke(context, a, b);
    }
};

When the compiler sees us using DynamicQSortCallback<0x00>(), it will generate a function DynamicQSortCallback<0x00>::GeneratedStaticFunction, and when it sees us using DynamicQSortCallback<0x01>(), it will generate a different function with a different context. This means the following holds true:

    &DynamicQSortCallback<0x00>::GeneratedStaticFunction != &DynamicQSortCallback<0x01>::GeneratedStaticFunction != ... != &DynamicQSortCallback<n>::GeneratedStaticFunction

Armed with this information you should be able to come up with a solution on your own; but let's walk slowly through the following anyway. We start off with the two obvious declarations shown above:

typedef int (*LPFN_QSortCCallback)(const void* a, const void* b);
typedef int (ClassWithCallback::*LPFN_QSortMemberFunctionCallback)(const void* a, const void* b);

We need a class that holds the state of the C++ member function callback. It will do a couple of things in addition to this:

This is the code:

// this object holds the state for a C++ member function callback in memory
class QsortCallbackBase
{
public:
    // input: pointer to a unique C callback. 
    QsortCallbackBase(LPFN_QSortCCallback pCCallback)
        :	m_pClass( NULL ),
            m_pMethod( NULL ),
            m_pCCallback( pCCallback )
    {
    }

    // when done, remove allocation of the callback
    void Free()
    {
        m_pClass = NULL;
        // not clearing m_pMethod: it won't be used, since m_pClass is NULL and so this entry is marked as free
    }

    // when free, allocate this callback
    LPFN_QSortCCallback Reserve(ClassWithCallback* instance, LPFN_QSortMemberFunctionCallback method)
    {
        if( m_pClass )
            return NULL;

        m_pClass = instance;
        m_pMethod = method;
        return m_pCCallback;
    }

protected:
    static int StaticInvoke(int context, const void* a, const void* b);

private:
    LPFN_QSortCCallback m_pCCallback;
    ClassWithCallback* m_pClass;
    LPFN_QSortMemberFunctionCallback m_pMethod;
};

Next, we come up with a slight modification of our template shown above.

template <int context> class DynamicQSortCallback : public QsortCallbackBase
{
public:
    DynamicQSortCallback()
        :   QsortCallbackBase(&DynamicQSortCallback<context>::GeneratedStaticFunction)
    {
    }

private:
    static int GeneratedStaticFunction(const void* a, const void* b)
    {
        return StaticInvoke(context, a, b);
    }
};

There are two important changes here:

  1. The class is derived from QsortCallbackBase, so it actually can be accessed like a slot.
  2. The constructor passes a pointer to the unique C function to the base data.

Now as for implementing the actual callback: let's first look at the interface definition for it:

class QSortMemberFunctionCallback
{
public:
    QSortMemberFunctionCallback(ClassWithCallback* instance, LPFN_QSortMemberFunctionCallback method);
    ~QSortMemberFunctionCallback();

public:
    operator LPFN_QSortCCallback() const
    {
        return m_cbCallback;
    }

    bool IsValid() const
    {
        return m_cbCallback != NULL;
    }

private:
    LPFN_QSortCCallback m_cbCallback;
    int m_nAllocIndex;

private:
    QSortMemberFunctionCallback( const QSortMemberFunctionCallback& os );
    QSortMemberFunctionCallback& operator=( const QSortMemberFunctionCallback& os );
};

Essentially, this is a simple decorator class: the constructor maps the input - a C++ class pointer and a C++ member function pointer - and identifies the unique C callback function for it. The rest of the class is just "mechanics": being able to call the function, checking if the mapping was successful.

Next, we create an array of up to 16 slots. That means, up to 16 functions can be using this technique either in parallel or recursive. Obviously, the number of slots is an implementation issue: you can easily use macros to provide space for much more allocations.

static QsortCallbackBase* AvailableCallbackSlots[] = {
	new DynamicQSortCallback<0x00>(),
	new DynamicQSortCallback<0x01>(),
	new DynamicQSortCallback<0x02>(),
	new DynamicQSortCallback<0x03>(),
	new DynamicQSortCallback<0x04>(),
	new DynamicQSortCallback<0x05>(),
	new DynamicQSortCallback<0x06>(),
	new DynamicQSortCallback<0x07>(),
	new DynamicQSortCallback<0x08>(),
	new DynamicQSortCallback<0x09>(),
	new DynamicQSortCallback<0x0A>(),
	new DynamicQSortCallback<0x0B>(),
	new DynamicQSortCallback<0x0C>(),
	new DynamicQSortCallback<0x0D>(),
	new DynamicQSortCallback<0x0E>(),
	new DynamicQSortCallback<0x0F>(),
};

Now we begin putting it all together. The StaticInvoke used by our slot is simple: it has a context, it simply looks up the data for that context and invokes it:

int QsortCallbackBase::StaticInvoke(int context, const void* a, const void* b)
{
    return ((AvailableCallbackSlots[context]->m_pClass)->*(AvailableCallbackSlots[context]->m_pMethod))(a, b);
}

The decorator class uses a standard RAII pattern to allocate / free the slot:

QSortMemberFunctionCallback::QSortMemberFunctionCallback(ClassWithCallback* instance, LPFN_QSortMemberFunctionCallback method)
{
    int imax = sizeof(AvailableCallbackSlots)/sizeof(AvailableCallbackSlots[0]);
    for( m_nAllocIndex = 0; m_nAllocIndex < imax; ++m_nAllocIndex )
    {
        m_cbCallback = AvailableCallbackSlots[m_nAllocIndex]->Reserve(instance, method);
        if( m_cbCallback != NULL )
            break;
    }
}

QSortMemberFunctionCallback::~QSortMemberFunctionCallback()
{
    if( IsValid() )
    {
        AvailableCallbackSlots[m_nAllocIndex]->Free();
    }
}

And that's it! Download the sourcecode + VS2010 project + binaries here.