views:

101

answers:

4
+5  Q: 

Generic callbacks

Extends

Related

So, I'm trying to learn template metaprogramming better and I figure this is a good exercise for it.

I'm trying to write code that can callback a function with any number of arguments I like passed to it.

// First function to call
int add( int x, int y ) ;

// Second function to call
double square( double x ) ;

// Third func to call
void go() ;

The callback creation code should look like:

// Write a callback object that
// will be executed after 42ms for "add"
Callback<int, int, int> c1 ;
c1.func = add ;
c1.args.push_back( 2 );  // these are the 2 args
c1.args.push_back( 5 );  // to pass to the "add" function
                         // when it is called

Callback<double, double> c2 ;
c2.func = square ;
c2.args.push_back( 52.2 ) ;

What I'm thinking is, using template metaprogramming I want to be able to declare callbacks like, write a struct like this (please keep in mind this is VERY PSEUDOcode)

<TEMPLATING ACTION <<ANY NUMBER OF TYPES GO HERE>> >
struct Callback
{
    double execTime ; // when to execute
    TYPE1 (*func)( TYPE2 a, TYPE3 b ) ;

    void* argList ;   // a stored list of arguments
                      // to plug in when it is time to call __func__
} ;

So for when called with

Callback<int, int, int> c1 ;

You would automatically get constructed for you by < HARDCORE TEMPLATING ACTION > a struct like

struct Callback
{
    double execTime ; // when to execute
    int (*func)( int a, int b ) ;

    void* argList ;   // this would still be void*,
                      // but I somehow need to remember
                      // the types of the args..
} ;

Any pointers in the right direction to get started on writing this?

+1  A: 

Look at boost::bind. I have little more to say… time is probably best spent poring over their source and trying to reimplement it, if you really want to understand the internals. But given how well they've polished it, reimplementation is only an academic pursuit.

Potatoswatter
+2  A: 

You can do this with variadic templates, which your compiler may not support. I've never used them myself and thus may get some of the details wrong, but I'll try to describe them.

Variadic templates use the "..." operator. Within a template declaration (or other type expressions), ellipses indicate that the formal parameter may take any number of arguments.

template <typename ... Args>
class Variadic {
public:
   operator()(Args&& ... args);
};

Within a function call expression, ellipses unpack their left argument.

Variadic<Args>::operator(Args&& ... args) {
    func(args...);
}

To forward, you might need to use std::forward; this is one area where my knowledge grows fuzzy. Put this together, and we get:

template <typename ReturnValue, typename ... Args>
class Callback {
    typedef ReturnValue (*Func)(Args ... args);

    double execTime;
    Func func;
    Args... args;

public:
    Callback(double et, Func f) : execTime(et), func(f) {}
    ReturnValue operator()(Args&& ... a);
    ReturnValue operator()();
};

template <typename ReturnValue, typename ... Args>
ReturnValue Callback<ReturnValue, Args>::operator()(Args&& ... a) {
    return (*func)(std::forward(a)...);
}
template <typename ReturnValue, typename ... Args>
ReturnValue Callback<ReturnValue, Args>::operator()() {
    return operator(*func)(args...);
}
outis
Variadic templates are part of C++0x. A compiler supporting them (or even a compiler with less C++0x support) will also include C++0x function objects and `std::bind`, which is similar to `boost::bind` and implements what you're talking about.
Potatoswatter
A: 

C++ 0x adds variadic templates, which directly support a template that takes an arbitrary number of parameters. Without that, you can use partial specialization to simulate it, though it requires a separate specialization for each number of parameters. For example, you could support from 1 to 3 parameters like this:

class null_class {};

template <class func, class arg1, class arg2, class arg3>
class callback_t { 
    func f;
    arg1 a;
    arg2 b;
    arg3 c;
public:
    callback_t(func f, arg1 a, arg2 b, arg3 c) : f(f), a(a), b(b), c(c) {}
    double operator()() const { return f(a, b, c); }
};

template <class func, class arg1, class arg2> 
class callback_t<func, arg1, arg2, null_class> { 
    func f;
    arg1 a;
    arg2 b;
public:
    callback_t(func f, arg1 a, arg2 b) : f(f), a(a), b(b) {}
    double operator()() const { return f(a, b); }
};

template <class func, class arg1> 
class callback_t<func, arg1, null_class, null_class> {
    func f;
    arg1 a;
public:
    callback_t(func f, arg1 a) : f(f), a(a) {}
    double operator()() const { return f(a); }
};

template <class func, class arg1, class arg2, class arg3>
callback_t<func, arg1, arg2, arg3> 
callback(func f, arg1 a, arg2 b, arg3 c) { 
    return callback_t<func, arg1, arg2, arg3>(f, a, b, c);
}

template <class func, class arg1, class arg2>
callback_t<func, arg1, arg2, null_class> 
callback(func f, arg1 a, arg2 b) {
    return callback_t<func, arg1, arg2, null_class>(f, a, b);
}

template <class func, class arg>
callback_t<func, arg, null_class, null_class>
callback(func f, arg a) {
    return callback_t<func, arg, null_class, null_class>(f, a);
}

#ifdef TEST
#include <iostream>

double square(double d) { 
    return d * d;
}

double add(double a, double b) { 
    return a + b;
}

double sum(double a, double b, double c) { 
    return a + b + c;
}

int main() {
    double a = 2.0, b = 3.0, c=4.0;

    double d = callback(square, a)();
    double e = callback(add, b, c)();
    double f = callback(sum, a, b, c)();

    std::cout << "2.0 squared = " << d << "\n";
    std::cout << "3.0 + 4.0 = " << e << "\n";
    std::cout << "Sum = " << f << "\n";
    return 0;
}

#endif

The return type can be templated as well, but I've left that out for the sake of simplicity (or at least reduced complexity).

Jerry Coffin
A: 

To begin with, you should check out Boost.Function, since it is about wrapping functions automatically, it will give you ideas I think ;)

Second, your syntax is a bit awkward. You can perfectly use functions signatures as part as the template parameters, which nicely deals with the problem of variadic templates since it allows you to pass an arbitrary number of types ;)

Callback< int(int,int) > callback;

Would indicate that your callback will take a pointer to a function with a signature similar to your add: int add(int, int). I actually prefer this syntax since it makes it clearer what we are passing on.

Before we start though I have a question: what do you want to do about the return type ?

1. References

Then there are things like the Boost.Fusion library which could help you a lot (basically, tuples).

Also, check out Boost.FunctionTypes which offers facilities to analyze a function signature.

2. On the road again

// It is nice to have a base class
// Generally callbacks do not return anything though...
struct CallbackBase
{ 
  virtual ~CallbackBase();
  virtual void execute() const = 0;
};

namespace func_ = boost::function_types;

template <
  class Parameters,
  class N = typename mpl_::minus<
    typename mpl_::size< Parameters >::type,
    mpl_::size_t<1>
  >::type
>
class Streamer
{
public:
  typedef Streamer< Parameters, typename mpl_::minus<N,1>::type > next_type;
  typedef typename mpl_::size< Parameters >::type size_type;
  typedef typename mpl_::minus< size_type, N >::type index_type;
  typedef typename mpl_::at<Parameters, index_type>::type arg_type;

  Streamer(Parameters& p): mParameters(p) {}

  next_type operator<<(arg_type arg)
  {
    boost::fusion::at_c<index_type>(mParameters) = arg;
    return next_type(mParameters);
  }

private:
  Parameters& mParameters;
};

template <class Parameters>
struct Streamer<Paramaters,0>
{
  Streamer(Parameters&) {}
};


template <class Function>
class Callback: public CallbackBase
{
public:
  typedef typename func_::result_type<Function>::type result_type;
  typedef typename func_::parameters_type<Function>::type parameters_type;
  typedef typename func_::function_pointer<
    typename func_::components<Function>::type
  >::type function_pointer;

  Callback(function_pointer f): mFunction(f) {}

  virtual void execute() const
  {
    mReturn = Invoke<function_pointer>::Do(f,mParameters);
  }

  Streamer<parameters_type> operator<<(typename mpl_::at<parameters_type, 0>::type arg)
  {
    boost::fusion::at_c<0>(mParameters) = arg;
    return Streamer<parameters_type>(mParameters);
  }

private:
  function_pointer f;
  result_type mResult;
  parameters_type mParameters;
};

Well, that's how far I went. I haven't dealt with the actual invocation which require unpacking the tuple to pass the arguments to the function.

So far usage would be:

int add(int,int);

void pass(const CallbackBase& b);

int main(int argc, char* argv[])
{
  Callback<int(int,int)> c(&add);
  c << 2 << 4;
  pass(c);
}

I strongly encourage you to delve into Boost.Fusion if you wish to pursue your studies in this domain, as pure template metaprogramming is often fruitless if you can't bring the result into the runtime world :)

Matthieu M.