Общие обратные вызовы - PullRequest
5 голосов
/ 21 марта 2010

Расширяет

Относящиеся

Итак, я пытаюсь лучше изучить шаблонное метапрограммирование и считаю, что это хорошее упражнение для него.

Я пытаюсь написать код, который может вызвать функцию с любым количеством аргументов, которые мне нравятся.

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

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

// Third func to call
void go() ;

Код создания обратного вызова должен выглядеть следующим образом:

// 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 ) ;

Я имею в виду, используя шаблонное метапрограммирование Я хочу иметь возможность объявлять обратные вызовы, например, написать такую ​​структуру (имейте в виду, что это ОЧЕНЬ 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__
} ;

То есть, когда вызывается с

Callback<int, int, int> c1 ;

Вы автоматически создадите для вас структуру, подобную

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..
} ;

Какие-нибудь указатели в правильном направлении, чтобы начать писать это?

Ответы [ 4 ]

2 голосов
/ 21 марта 2010

Вы можете сделать это с шаблонами переменных , которые ваш компилятор может не поддерживать. Я никогда не использовал их сам, и поэтому могу ошибиться в некоторых деталях, но я постараюсь описать их.

В шаблонах Variadic используется оператор "...". В объявлении шаблона (или других выражениях типов) эллипсы указывают, что формальный параметр может принимать любое количество аргументов.

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

В выражении вызова функции эллипсы распаковывают свой левый аргумент.

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

Для пересылки вам может понадобиться std::forward; это одна из областей, где мои знания становятся нечеткими. Сложите это вместе, и мы получим:

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...);
}
1 голос
/ 21 марта 2010

Посмотрите на boost :: bind . У меня есть немного больше, чтобы сказать ... время, вероятно, лучше всего потратить на изучение их источника и попыток переопределить его, если вы действительно хотите понять внутренности. Но, учитывая, насколько хорошо они его отшлифовали, повторная реализация - это всего лишь академическое занятие.

0 голосов
/ 21 марта 2010

Для начала вам следует проверить Boost.Function , так как речь идет об автоматическом обёртывании функций, это даст вам идеи, я думаю;)

Во-вторых, ваш синтаксис немного неловкий. Вы можете прекрасно использовать сигнатуры функций как часть параметров шаблона, что хорошо решает проблему шаблонов с переменными параметрами, поскольку позволяет передавать произвольное количество типов;)

Callback< int(int,int) > callback;

Указывает, что ваш обратный вызов будет принимать указатель на функцию с сигнатурой, аналогичной вашей add: int add(int, int). Я на самом деле предпочитаю этот синтаксис, поскольку он проясняет то, что мы передаем.

Прежде чем мы начнем, у меня есть вопрос: что вы хотите сделать с типом возвращаемого значения?

1. Ссылки

Тогда есть такие вещи, как библиотека Boost.Fusion , которая может вам очень помочь (в основном, кортежи).

Кроме того, посмотрите Boost.FunctionTypes , который предлагает средства для анализа сигнатуры функции.

2. Снова в пути

// 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;
};

Ну, вот как далеко я зашел. Я не имел дело с фактическим вызовом, который требует распаковки кортежа для передачи аргументов функции.

Пока что использование будет:

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);
}

Я настоятельно рекомендую вам углубиться в Boost.Fusion, если вы хотите продолжить обучение в этой области, поскольку чистое метапрограммирование на основе шаблонов часто бесполезно, если вы не можете принести результат в мир времени выполнения:)

0 голосов
/ 21 марта 2010

C ++ 0x добавляет переменные шаблоны, которые напрямую поддерживают шаблон, который принимает произвольное количество параметров.Без этого вы можете использовать частичную специализацию для симуляции, хотя она требует отдельной специализации для каждого числа параметров.Например, вы можете поддерживать от 1 до 3 параметров, таких как:

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

Тип возвращаемого значения также может быть шаблонным, но я оставил это для простоты (или, по крайней мере, для уменьшения сложности).

...