views:

251

answers:

3

Hi all, I'm writing some callback implementation in C++.

I have an abstract callback class, let's say:

/** Abstract callback class. */
class callback {
public:

    /** Executes the callback. */
    void call() { do_call(); };
protected:

    /** Callback call implementation specific to derived callback. */
    virtual void do_call() = 0;
};

Each callback I create (accepting single-argument functions, double-argument functions...) is created as a mixin using one of the following:

/** Makes the callback a single-argument callback. */
template <typename T>
class singleArgumentCallback {
protected:
    /** Callback argument. */
    T arg;

public:
    /** Constructor. */
    singleArgumentCallback(T arg): arg(arg) { }
};

/** Makes the callback a double-argument callback. */
template <typename T, typename V>
class doubleArgumentCallback {
protected:
    /** Callback argument 1. */
    T arg1;

    /** Callback argument 2. */
    V arg2;

public:
    /** Constructor. */
    doubleArgumentCallback(T arg1, V arg2): arg1(arg1), arg2(arg2) { }
};

For example, a single-arg function callback would look like this:

/** Single-arg callbacks. */
template <typename T>
class singleArgFunctionCallback:
    public callback,
    protected singleArgumentCallback<T> {

    /** Callback. */
    void (*callbackMethod)(T arg);

public:
   /** Constructor. */
    singleArgFunctionCallback(void (*callback)(T), T argument):
        singleArgumentCallback<T>(argument),
        callbackMethod(callback) { }

protected:
    void do_call() {
        this->callbackMethod(this->arg);
    }
};

For user convenience, I'd like to have a method that creates a callback without having the user think about details, so that one can call (this interface is not subject to change, unfortunately):

void test3(float x) { std::cout << x << std::endl; }
void test5(const std::string& s) { std::cout << s << std::endl; }

make_callback(&test3, 12.0f)->call();
make_callback(&test5, "oh hai!")->call();

My current implementation of make_callback(...) is as follows:

/** Creates a callback object. */
template <typename T, typename U> callback* make_callback(
    void (*callbackMethod)(T), U argument) {
    return new singleArgFunctionCallback<T>(callbackMethod, argument);
}

Unfortunately, when I call make_callback(&test5, "oh hai!")->call(); I get an empty string on the standard output. I believe the problem is that the reference gets out of scope after callback initialization.

I tried using pointers and references, but it's impossible to have a pointer/reference to reference, so I failed. The only solution I had was to forbid substituting reference type as T (for example, T cannot be std::string&) but that's a sad solution since I have to create another singleArgCallbackAcceptingReference class accepting a function pointer with following signature:

void (*callbackMethod)(T& arg);

thus, my code gets duplicated 2^n times, where n is the number of arguments of a callback function.

Does anybody know any workaround or has any idea how to fix it? Thanks in advance!

+1  A: 

Boost.TypeTraits might help. add_reference converts concrete types to reference types while leaving reference types as-is.

Marcelo Cantos
+3  A: 

The problem is that in make_callback(), T becomes const std::string&, which in turn becomes T in your singleArgumentCallback. U, however, is a const char*, so a temporary std::string object is created and bound to that reference in singleArgumentCallback. When make_callback() finishes, that temporary is destroyed, leaving the created singleArgumentCallback object with a reference to a no longer existing object.

What you will have to do is to first remove references (and, maybe, cv qualifiers) from the types passed into make_callback(). As Marcelo suggested, Boost.TypeTraits can help you do this, but if you want, it isn't hard to cook up something on your own:

template< typename T > struct remove_ref     { typedef T result_type; };
template< typename T > struct remove_ref<T&> { typedef T result_type; };

Then change make_callback() to:

template <typename T, typename U>
callback* make_callback(void (*callbackMethod)(T), U argument)
{
    typedef typename remove_ref<T>::result_type  arg_type;
    return new singleArgFunctionCallback<arg_type>(callbackMethod, argument);
}
sbi
Great, thanks for this hint! It did not work out-of-the-box, but after some... let's say, 'fixes', I got it. The full solution is posted below.Unfortunately, I'm not allowed to use Boost. But thanks, I'll remember about that in future!
Karol
A: 

Thanks to sbi, I got this stuff working :-)

The solution I ended up with is here:

template <typename T> struct removeRef     { typedef T resultType; };
template <typename T> struct removeRef<T&> { typedef T resultType; };

/** Single-arg callbacks. */
template <typename T, typename U>
class singleArgFunctionCallback:
    public callback,
    protected singleArgumentCallback<U> {

    /** Callback. */
    void (*callbackMethod)(T arg);

public:
    /** Constructor. */
    singleArgFunctionCallback(void (*callback)(T), U argument):
        singleArgumentCallback<U>(argument),
        callbackMethod(callback) { }

protected:
    void do_call() {
        this->callbackMethod(this->arg);
    }
};

template <typename T, typename U>
callback* make_callback(void (*callbackMethod)(T), U argument) {
    typedef T ArgumentType;
    typedef typename removeRef<T>::resultType StrippedArgumentType;
    return new singleArgFunctionCallback<ArgumentType, StrippedArgumentType>(callbackMethod, argument);
}

If anybody sees any possible improvements, I'd be happy to learn!

Thanks all, Karol

Karol