С++: создать диспетчер пользовательских функций из шаблона с переменным числом аргументов

У меня есть несколько функций, которые считывают различные типы из сериализованных данных, например:

class DataDeserializer
{
    int getInt();
    std::string getString();
    MyClass getMyClass();
}

Затем у меня есть различные функции обратного вызова, которые принимают произвольные параметры, например:

void callbackA (int, int, int);
void callbackB (int, std::string);
void callbackC (std::string, int, MyClass, int);

Я хочу вызывать различные обратные вызовы с аргументами, считанными из потока десериализованных данных. Я хотел бы максимально автоматизировать шаблонный код. Я подумал, может быть, я мог бы использовать шаблоны. Если бы у меня был какой-то класс Dispatcher, например:

template <SOMETHING??> class Dispatcher
{
    void dispatch()
    {
        // ???? 
    }

    SOMEFUNCTIONTYPE callback;
    DataDeserializer myDeserializer;
};

Затем объявите различные конкретные диспетчеры:

Dispatcher<int,int,int>                  myDispatcherA (deserializer, callbackA);
Dispatcher<int,std::string>              myDispatcherB (deserializer, callbackB);
Dispatcher<std::string,int,MyClass,int>  myDispatcherC (deserializer, callbackC);

Затем, когда я хочу отправить, я просто звоню:

myDispatcherB.dispatch();

который внизу расширится до чего-то вроде этого:

void dispatch()
{
    callback (myDeserializer.getString(), myDeserializer.getInt(), myDeserializer.getMyClass(), myDeserializer.getInt());
}

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


person gimmeamilk    schedule 17.09.2013    source источник
comment
Откуда бы вы взяли, например, строку, int, some-class и int? Мой stream_function (найденный здесь) может помочь.   -  person Xeo    schedule 17.09.2013
comment
Отредактировано, чтобы сделать использование десериализатора более понятным. Сам десериализатор представляет собой код черного ящика.   -  person gimmeamilk    schedule 17.09.2013


Ответы (2)


Я сделал нечто подобное для своего класса stream_function. Основная идея заключается в том, что вы передаете тип шаблону функции, который делает The Right Thing™, и расширяете этот вызов:

callback(magic<Args>(/* sth */)...);

Однако, если ваши функции не являются чистыми и изменяют какое-то состояние, и поэтому требуют, чтобы они вызывались в правильном порядке, вам придется форсировать этот порядок с помощью некоторых трюков.

Если вы используете Clang, это довольно просто, так как для списков инициализации в фигурных скобках выполняется оценка слева направо. Это позволяет вам просто использовать небольшой вспомогательный тип

struct invoker{
  template<class F, class... Args>
  invoker(F&& f, Args&&... args){ f(std::forward<Args>(args)...); }
};

а затем сделать

invoker{ callback, magic<Args>(/* sth */)... };

К сожалению, GCC пока не реализует эту функцию, поэтому приходится прибегать к принудительному исполнению заказов вручную. Это можно сделать с помощью небольшой вспомогательной структуры, которая представляет собой просто список типов, но позволяет делать некоторые полезные вещи:

  • видеть, когда пакет пуст (types<>), и
  • обработать Args в рекурсивном режиме "голова-потом-хвост"

template<class...> struct types{};

template<class... Args>
struct dispatcher{
    std::function<void(Args...)> f;

    void call(){ _call(types<Args...>{}); }
private:
    // take head, produce value from it, pass after other values
    template<class Head, class... Tail, class... Vs>
    void _call(types<Head, Tail...>, Vs&&... vs){
        _call(types<Tail...>{}, std::forward<Vs>(vs)..., get_value<Head>());
    }

    // no more values to produce, forward to callback function
    template<class... Vs>
    void _call(types<>, Vs&&... vs){ f(std::forward<Vs>(vs)...); }
};

Живой пример.

person Xeo    schedule 20.09.2013

Что-то вроде этого может помочь вам

template<typename T>
T get_value(Deserializer&);

template<>
int get_value(Deserializer& d)
{
   return d.getInt();
}

template<>
std::string get_value(Deserializer& d)
{
   return d.getString();
}

template<typename... Args>
class Dispatcher
{
public:
   template<typename Functor>
   Dispatcher(Deserializer& d, const Functor& cb) : myDeserializer(d), callback(cb)
   {
   }
   void dispatch()
   {
      callback(get_value<Args>(myDeserializer)...);
   }
private:
   std::function<void(Args...)> callback;
   Deserializer myDeserializer;
};

Живой пример

person ForEveR    schedule 17.09.2013
comment
Обратите внимание, что у этого есть та же проблема, что и у моего stream_function - порядок оценки вызовов get_value не указан. - person Xeo; 17.09.2013
comment
Да, это почти то, что я ищу, но я обнаружил, что аргументы передаются в обратном порядке. Есть ли способ обеспечить порядок оценки? - person gimmeamilk; 17.09.2013
comment
@gimmeamilk: посмотрите мой ответ, на который я ссылался в своем предыдущем комментарии, если вы используете Clang. Если вы используете GCC, вам нужно будет вручную создать пакет и рекурсивно вызывать одну функцию за раз. - person Xeo; 17.09.2013
comment
@Xeo, к сожалению, это gcc. Я не уверен, каким будет рекурсивное решение. Я могу вызывать каждый get_value() по одному поверх рекурсии, но как передать все значения обратному вызову одновременно? - person gimmeamilk; 17.09.2013
comment
@gimmeamilk: вы бы объединили их в вариативный пакет. Вот пример. - person Xeo; 17.09.2013
comment
@Xeo ты гений! На самом деле я не очень понимаю, что делает этот код (я думаю, потребуется немного времени, чтобы его переварить ...), но, похоже, он работает отлично. Огромное спасибо. - person gimmeamilk; 17.09.2013
comment
@gimmeamilk: types - это просто так называемый тип тега, который позволяет мне делать две вещи: я могу видеть, когда пакет пуст (types<>), и я могу обрабатывать Args в рекурсивном режиме. мода. - person Xeo; 17.09.2013
comment
@Xeo хороший пример. Пожалуйста, добавьте свой ответ с этим кодом, и тогда я удалю свой ответ. - person ForEveR; 18.09.2013