Распаковка данных для вызовов функций шаблона Variadic, хранящихся в виде массива (цель: RPC)

Идея состоит в том, чтобы создать следующую функциональность (выглядит легко)

void test(int , float , char* ){ /*gets called*/ }

void main()
{
RegisterRPC( test  ,  int , float , char* )
}

Псевдокод для регистрации функции:

std::map<std::string , std::function<void()> > functionarray;

template<typename F, typename... Args>
void RegisterRPC( F , Args )
{
// somehow add to functionarray
}

Затем, когда данные поступают из сети, данные должны быть разложены для вызова test с соответствующими аргументами.

ProcessData(data)
{
data.begin();
functionarray[data.get<char*>()] (
data.get<int>() ,
data.get<float>() ,
data.get<char*>() ); // the RegisterRPC parameters
}

Я уже обнаружил, что шаблоны Variadic могут хранить аргументы

расширенный список параметров для вариационного шаблона

И это может разложить аргументы в классы

Как я могу выполнить итерацию по списку аргументов упакованного вариадического шаблона?

Так что я верю, что это возможно — просто я не понимаю, как ..
Надеюсь, кто-нибудь может помочь.

Изменить: Если кто-то заинтересован в полном решении:

#include <tuple>
#include <iostream>
#include <strstream>
#include <istream>
#include <sstream>
#include <string>

// ------------- UTILITY---------------
template<int...> struct index_tuple{};

template<int I, typename IndexTuple, typename... Types>
struct make_indexes_impl;

template<int I, int... Indexes, typename T, typename ... Types>
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...>
{
typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type;
};

template<int I, int... Indexes>
struct make_indexes_impl<I, index_tuple<Indexes...> >
{
typedef index_tuple<Indexes...> type;
};

template<typename ... Types>
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...>
{};

// ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------

using namespace std;

template<class Ret, class... Args, int... Indexes >
Ret apply_helper(Ret(*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup)
{
return pf(forward<Args>(get<Indexes>(tup))...);
}

template<class Ret, class ... Args>
Ret apply(Ret(*pf)(Args...), const tuple<Args...>&  tup)
{
return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}

template<class Ret, class ... Args>
Ret apply(Ret(*pf)(Args...), tuple<Args...>&&  tup)
{
return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}

// --- make tuple ---

template <typename T> T read(std::istream& is)
{
T t; is >> t; cout << t << endl; return t;
}

template <typename... Args>
std::tuple<Args...> parse(std::istream& is)
{
return std::make_tuple(read<Args>(is)...);
}

template <typename... Args>
std::tuple<Args...> parse(const std::string& str)
{
std::istringstream ips(str);
return parse<Args...>(ips);
};

// ---- RPC stuff

class DataSource
{
std::string data;
public:
DataSource(std::string s) { data = s; };
template<class...Ts> std::tuple<Ts...> get() {  return parse<Ts...>(data);  };
};

std::map<std::string, std::function<void(DataSource*)> > functionarray;

template<typename... Args, class F>
void RegisterRPC(std::string name, F f) {
functionarray[name] = [f](DataSource* data){apply(f, data->get<Args...>()); };
}

// --------------------- TEST ------------------

void one(int i, double d, string s)
{
std::cout << "function one(" << i << ", " << d << ", " << s << ");\n";
}

int main()
{
RegisterRPC<int, double, string>("test1", one);
DataSource* data=new DataSource("5 2 huhu");
functionarray["test1"](data);
system("pause");
return 0;
}

// --------------------- TEST ------------------

1

Решение

Сначала напишите «вызов с кортежем». Это берет вызываемый объект f и кортеж tи звонки f( std::get<0>(t), std::get<1>(t), ... ),

Вот является одной из многих таких реализаций при переполнении стека. Вы можете написать лучшую версию на C ++ 14 или подождать, пока она появится на C ++ 1z.

Во-вторых, напишите data.get<std::tuple<A,B,C,...>>() который возвращает tuple типа A,B,C,..., Это просто:

template<class...Ts>
std::tuple<Ts...> DataSource::get() {
return std::tuple<Ts...>{get<Ts>()...}; // some compilers get order here wrong, test!
}

теперь наш массив функций выглядит так:

std::map<std::string , std::function<void(DataSource*)> > functionarray;

template<class...Args, class F>
std::function<void(DataSource*)> from_source( F f ) {
// `[f = std::move(f)]` is C++14.  In C++11, just do `[f]` instead
return [f = std::move(f)](DataSource* data){
call_from_tuple( f, data->get<std::tuple<Args...>>() );
};
}

template<typename... Args, class F>
void RegisterRPC( F f ) {
functionarray.push_back( from_source<Args...>( std::move(f) ) );
}

и конечное использование:

void test(int , float , char* ){ /*gets called*/ }

void main()
{
RegisterRPC<int,float,char*>( test )
}

Я рекомендую против использования char*, Я бы использовал std::string, или же std::vector<char> или даже std::unique_ptr<char[]>, так что время жизни предельно ясно.

Хитрость заключается в том, что мы стираем в точке, где мы знаем информацию о типе, где мы заключаем функцию в оболочку. Там мы даем ему инструкции о том, как получать типы из источника данных и вызывать себя, оставляя после себя функцию типа «источник данных -> ничего».

Мы берем «Ts … -> nothing» (ваш F) и «(DataSource -> Ts) …» (поток данных по сети) и объединяем его в «DataSource -> nothing» ( std::function вы храните).

2

Другие решения