Delegation

Last time, I spoke about lambdas, and left you with this comment about other features of lambdas.

One that’s particularly worth bearing in mind is the ability to pass this as a lambda parameter. That means you can get access to more than just the enclosing scope variables, you can get access to the enclosing object.

It might not be immediately obvious why it’s so useful to be able to put an object reference in a lambda. The fact is that it doesn’t have to be [this] that you pass; any lambda with a reference to an object has effectively created a delegate. This is particularly important if you create a lambda that is intended to be longer lived, that you will return or pass around rather than use instantly and discard.

Before I begin on that subject, let’s look a little bit at functors. I previously was a little cavalier and treated them as if they were indistinguishable from function pointers. Unfortunately that’s only true in the no-parameter-constructor case. It’s definitely not true that a pointer to a class with an operator()() implementation is the same as a pointer to a function – a pointer to a function has a real address; an operator()() of an object cannot have its address taken. Lambda references are different again. C++11 fortunately introduces a class that let’s us ignore the differences, std::function.

#include <functional>

void functionTakingFunction( std::function<void (int)> f ) {
    f(10);
}

The parameter here can be a C-compatible pointer-to-function, a functor, or a lambda. We don’t need to care. Back to the plot…

A delegate lets you tell it about object A, and then pass the delegate as a callback to object B – object B then never needs to know about object A – in particular, that it is calling an instance rather than a static member.

Here’s an example of delgation that you may already have used without realising it was delegation. Imagine you are wrapping a C library in a C++ object. The library implements a callback facility to, say, let you know when it has finished some task.

library_taskfinished( libraryHandle, myFunction, myOpaquePtr );

You’ve wrapped the libraryHandle in an class and in the constructor you want to make sure that this callback gets routed to this instance. The problem is that instance methods are not normal functions, they require an instance pointer to be passed to them (the compiler generates that code for you). The solution is this:

class TLibrary {
  public:
    TLibrary() {
        libraryHandle = library_createhandle();
        library_taskfinished( libraryHandle, taskFinishedStatic, this );
    }
  private:
    static void taskFinishedStatic( void *opaque ) {
        TLibrary *objectPointer = reinterpret_cast<TLibrary*>(opaque);
        objectPointer->taskFinished();
    }
    virtual void taskFinished() = 0;

  private:
    struct sLibraryHandle *libraryHandle;
};

The static acts as half of a delegate. A static is a normal function pointer, whose address can be taken, so it can be used as a C callback. The library is willing to store an opaque pointer (this is almost universal for callback-using libraries) that it promises to hand back to us in the callback. The pointer is opaque to the library, but it is not opaque to taskFinishedStatic(), which casts it back to a TLibrary* instance pointer and calls the member function taskFinished(). We’re implementing a wrapper, so we can’t say what taskFinished() should do either – but it is a virtual member, so the user of the wrapper can implement as appropriate for them.

Now, imagine that the library didn’t take an opaque pointer. We would have no way of finding out the original object. Unless the library was a C++ library and accepted a std::function parameter; and then we could pass a real-life delegate.

class TLibrary {
  public:
    TLibrary() {
        libraryHandle = library_createhandle();
        library_taskfinished( libraryHandle,
            [this](){this->taskFinished();} );
    }
  private:
    virtual void taskFinished() = 0;

  private:
    struct sLibraryHandle *libraryHandle;
};

The delegate is a lambda, constructed inline with a note of its creating class. That note allows it, when it is later called by the library, to take the place of taskFinishedStatic(). It needs no cast because it knows the type of its parameter, this.

While this is a good example of delegation, it’s a bad practical example. It relied on the C library supporting a C++ class as a parameter – of course it won’t. It’s impossible that the C library function library_taskfinished() takes a std::function as its parameter.

However, let’s use the semi-delegate trick we used for TLibrary::taskFinishedStatic() to bridge the gap, but this time it will bridge the gap for std::function not just one specific case like TLibrary.

#include <functional>
#include <iostream>
using namespace std;

// Want a C-style function-pointer-accepting function
extern "C" int instant_callback( int (*cb)(void *), void *opaque) {
    // A real library would provide a function that let the caller set a
    // function and pointer to be used as a callback in response to some
    // event.  We're just testing, so rather than noting the callback
    // and opaque pointer for later, we just call it instantly
    return cb(opaque);
}

//
// Class: functor_as_c_function<>
// Description:
//  Turns a C-incompatible functor into a C-compatible callback.  The
//  trick is to have inserted a static (which is just a function) to
//  capture an opaque pointer back from the caller of the callback, and
//  then cast it to the C++ function type that we know it is, and call
//  that.
//
template<typename F>
class functor_as_c_function {
    using R = typename F::result_type;
  public:
    static R c_function( void *opaque ) {
        F &transparent = *(reinterpret_cast<F*>(opaque));
        return transparent();
    }
};

int main()
{
    // A functor, and a typedef for the callback-creating helper
    function<int()> mFunctor;
    typedef functor_as_c_function<decltype(mFunctor)> cf;
    
    // Lambdas are the easiest functors to create
    int x = 10;
    mFunctor = [&x](){ return x; };

    // Should be 10
    clog << instant_callback(cf::c_function, &mFunctor) << endl;

    x = 11;

    // Should display 11, as the functor took x by reference
    clog << instant_callback(cf::c_function, &mFunctor) << endl;

    return 0;
}

We can go a step further. We can solve the problem that is addressed in this article about partial functions in C. The problem addressed there is when a callback doesn’t even allow an opaque pointer to be passed. The library functions atexit() and signal() both have this drawback. The article solves the problem with a clever hack – by creating a function pointer that encodes parameter information in it. We can do better than that though, without needing to touch assembly.

We cheat by making the function call itself hold the opaque pointer. Effectively delegating again. Using templates we can get the compiler to manufacturer us such a function on the fly.

// This is a simulation of a really unpleasant callback; the library
// writer hasn't given us an opaque pointer.
extern "C" int bare_instant_callback( int (*cb)(void*) );

template<int(*opaqueFunction)(void*), void *GlobalOpaque>
class bare_wrapping_callback {
  public:
    static int callback_with_opaque() {
        return opaqueFunction(GlobalOpaque);
    }
};

In this way we’ve effectively stored the opaque pointer as a constant inside the function. The drawback here is that the GlobalOpaque has to be globally accessible.

This entry was posted in FussyLogic and tagged , , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

Post a Comment

You must be logged in to post a comment.