1

The idea is to create the following functionality (Looks easy)

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

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

Pseudo-code to register the function:

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

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

Then, when data comes from Network, the data needs to be decomposed to call test with the proper args.

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

I already found that Variadic Templates can store args

expanded parameter list for variadic template

And it can decopose args into classes

How can I iterate over a packed variadic template argument list?

So I believe its possible - just I dont get how.. Hope somebody can help.

Edit : In case somebody is interested in the complete solution :

#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 ------------------
Community
  • 1
  • 1
Marco Polo
  • 91
  • 5

1 Answers1

2

First, write a "call with tuple". This takes a callable object f and a tuple t, and calls f( std::get<0>(t), std::get<1>(t), ... ).

here is one of many such implementations on stack overflow. You can write a better one in C++14, or wait for it to arrive in C++1z.

Second, write data.get<std::tuple<A,B,C,...>>() that returns a tuple of type A,B,C,.... This is easy:

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

now our function array looks like this:

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

and end use is:

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

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

I recomment against using char*. I'd use std::string, or std::vector<char> or even std::unique_ptr<char[]>, so lifetime is extremely clear.

The trick is that we erase at the point where we know the type information, which is where we wrap the function. There, we give it instructions on how to get the types from the data source and call itself, leaving behind a function of type "data source -> nothing".

We take "Ts... -> nothing" (your F) and "(DataSource -> Ts)..." (the stream of data over the network) and compose it into "DataSource -> nothing" (the std::function you store).

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thank you for the quick answer. Great to see that RegisterRPC is possible with templates. So ProcessData is achieved by functionarray["some name"](DataSource* data) ? For strings, std:string is indeed better. – Marco Polo Sep 21 '15 at 20:13