views:

395

answers:

4

Hello all,

I am working on implementing a function that would execute another function a few seconds in the future, depending upon the user's input. I have a priority queue of a class (which I am calling TimedEvent) that contains a function pointer to the action I want it to execute at the end of the interval. Say for instance that the user wants the program to call a function "xyz" after 3 seconds, they would create a new TimedEvent with the time and the function pointer to xyz and add it to the priority queue (which is sorted by time, with the soonest events happening first).

I have been able to successfully get the priority queue to pop off the top element after the specified time, but am running into a wall here. The functions I want to call could take a variety of different parameters, from ones that take only a single integer to ones that take 3 integers, a string, etc. and also return different values (some ints, some strings, etc.). I have looked into va_lists (which I have no experience with), but this doesn't seem to be the answer, unless I'm missing something.

In summary (the TL;DR version):
I would like to be able to call these functions as "diverse" as these with the same function pointer:

void func1(int a, int b);<br/>
int func2(int a, string b, OtherClass c);

Am I on the right track with a va_list and a function callback? Can this be implemented easily (or at all)?

Thanks!

+1  A: 

What you're trying to do is almost impossible to get to work. You might want to consider packing your parameters into something like an std::vector<boost::any> instead.

Variable parameter lists are really the opposite of what you want. A variable parameter list allows a single function to be called from multiple sites, each with a unique set of parameters. What you want is to call multiple functions from a single site, each with a unique set of parameters -- and a variable parameter list just doesn't support that.

Jerry Coffin
+1  A: 

c/invoke is a library that lets you construct arbitrary function calls at runtime, but I think that's overkill in this case. It sounds like you should find a way to "normalize" the callback function's signature so that you can call it the same way every time with a list, structure, union or something that allows you to pass different data through the same interface.

Tim Sylvester
+3  A: 

I'm inferring here that these functions are API calls that you have no control over. I hacked up something that I think does more or less what you're looking for; it's kind of a rough Command pattern.

#include <iostream>
#include <string>

using namespace std;

//these are the various function types you're calling; optional
typedef int (*ifunc)(const int, const int);
typedef string (*sfunc)(const string&);

// these are the API functions you're calling
int func1(const int a, const int b) { return a + b; }
string func2(const string& a) { return a + " world"; }

// your TimedEvent is given one of these
class FuncBase
{
public:
  virtual void operator()() = 0;

};

// define a class like this for each function type
class IFuncWrapper : public FuncBase
{
public:
  IFuncWrapper(ifunc fp, const int a, const int b) 
    : fp_(fp), a_(a), b_(b), result_(0) {}

  void operator()() {
    result_ = fp_(a_, b_);
  }

  int getResult() { return result_; }

private:

  ifunc fp_;
  int a_;
  int b_;
  int result_;

};

class SFuncWrapper : public FuncBase
{
public:
  SFuncWrapper(sfunc fp, const string& a) 
  : fp_(fp), a_(a), result_("") {}

  void operator()() {
    result_ = fp_(a_);
  }

  string getResult() { return result_; }

private:

  sfunc fp_;
  string a_;
  string result_;

};

int main(int argc, char* argv[])
{
  IFuncWrapper ifw(func1, 1, 2);
  FuncBase* ifp = &ifw;

  // pass ifp off to your TimedEvent, which eventually does...
  (*ifp)();
  // and returns.

  int sum = ifw.getResult();
  cout << sum << endl;

  SFuncWrapper sfw(func2, "hello");
  FuncBase* sfp = &sfw;

  // pass sfp off to your TimedEvent, which eventually does...
  (*sfp)();
  // and returns.

  string cat = sfw.getResult();
  cout << cat << endl;

}

If you have a lot of functions returning the same type, you can define a subclass of FuncBase that implements the appropriate GetResult(), and wrappers for those functions can subclass it. Functions returning void would not require a GetResult() in their wrapper class, of course.

ceo
This is EXACTLY what I was looking for... thanks a bunch!
Ryan Zink
+3  A: 

I think boost::bind will be useful to you. For your application, you will probably want to bind all arguments when you create the functor, before putting it on the queue (that is, not use any _1 or _2 placeholders). I don't think you need anything as complicated as lambda expressions/abstractions, but it's good to understand what they are.

+1 ceo for the DIY approach. That will work too, but you have to do all the hard work yourself.

If you want to DIY though, I would suggest using templates instead of defining an xfunc and XFuncWrapper for each combination of types (see code below).

Also, I think allowing different return types is going to be pointless -- whatever code is de-queuing and calling the functions is going to be generic. Either it expects the same type of return from each function, or it expects them to be procedures (return void).

template<typename R>
class FuncWrapper0 : public FuncBase
{
public:
  typedef R (*func)();
  FuncWrapper0(func fp) : fp_(fp) { }
  void operator()() { result_ = fp_(); }
  R getResult() { return result_; }
private:
  func fp_;
  R result_;
};

template<typename R, typename P1>
class FuncWrapper1 : public FuncBase
{
public:
  typedef R (*func)(const P1 &);
  FuncWrapper1(func fp, const P1 &p1) : fp_(fp), p1_(p1) { }
  void operator()() { result_ = fp_(p1_); }
  R getResult() { return result_; }
private:
  func fp_;
  P1 p1_;
  R result_;
};

template<typename R, typename P1, typename P2>
class FuncWrapper2 : public FuncBase
{
public:
  typedef R (*func)(const P1 &, const P2 &);
  FuncWrapper2(func fp, const P1 &p1, const P2 &p2)
    : fp_(fp), p1_(p1), p2_(p2) { }
  void operator()() { result_ = fp_(p1_, p2_); }
  R getResult() { return result_; }
private:
  func fp_;
  P1 p1_;
  P2 p2_;
  R result_;
};
Dan
The API calls turn out to not have so many combinations of return types and parameters, so I ended up using ceo's approach. However, I can definitely see the advantage of using this method for projects with a bigger scope... thanks!
Ryan Zink
Nice work. I was trying to work up a templatized version of this, but it was getting a bit late by that point.
ceo