Start with a type bundle:
template<class...Ts>struct types{
using type=types;
enum{count = sizeof...(Ts)};
};
template<class T> struct tag{using type=T;};
Now we define all of our supported types in one global types bundle.
The index into that global types bundle is sent over the wire, and used to look up the deserialization code.
There should be a function boost::any read( wire_data, T* unused ) defined in your protocol namespace (for basic types and std types) and in the namespace of T (for other types) that reads wire data into a boost::any. wire_data is just a placeholder for whatever stuff you get off the wire that you turn into the T.
We turn the type index into a call to read via the magic switch technique:
template<size_t n> using index=std::integral_constant<size_t, n>;
template<class types, class T>
struct index_in;
template<class...Ts, class T>
struct index_in<types<T, Ts...>, T>:index<0> {};
template<class T0, class...Ts, class T1>
struct index_in<types<T0, Ts...>, T1>:index<
index_in<types<Ts...>, T1>::value+1
> {};
gives us the offset of a type T in types<Ts...>. Use that on the sending side to map your type to an index into the list.
On the other side, we have:
template<class types, size_t n>
struct type_at;
template<class types, size_t n>
using type_at_t=typename type_at<types,n>::type;
template<class T0, class...Ts>
struct type_at<types<T0, Ts...>,0>: tag<T0> {};
template<class T0, class...Ts, size_t n>
struct type_at<types<T0, Ts...>,n>:
type_at<types<Ts...>, n-1>
{};
which takes a types<Ts...> and an index and returns a type.
template<class types>
struct to_any {
template<size_t n>
struct worker {
boost::any operator()( wire_data w )const{
using protocol_ns::read;
return read( w, (type_at_t<types,n>*)nullptr );
}
};
};
which dispatches to read using ADL.
Now we write our quick magic switch:
namespace details {
template<template<size_t>class action, class indexes>
struct magic_switch;
template<template<size_t>class action, size_t... Is>
struct magic_switch<action, std::index_sequences<Is...>>
{
template<class...Ts, class R=std::result_of_t< action<max>(Ts...) >>
R operator()(size_t i, Ts&&... ts)const {
using entry = R(*)(std::remove_reference<Ts>*...);
entry table[] = {
[](std::remove_reference<Ts>*...args)->R{
return action<Is>{}( std::forward<Ts>(*args)... );
}...
};
if (i > sizeof(table)/sizeof(entry))
throw std::out_of_range("i");
return table[i]( (&ts)... );
}
};
}
template<template<size_t>class action, size_t max>
struct magic_switch:
details::magic_switch<action,std::make_index_sequence<max>>
{};
Then
magic_switch<
to_any<all_types_supported>::template worker,
all_types_supported::count
>
is the type of a stateless function object that, when passed n and wire_data, will call the appropriate read function for that type and return a boost::any.
Ok, now we are half way there.
The second half involves taking our function of signature Z(Args...), and writing a type eraser that takes a std::vector<boost::any> storing Args... and returns a boost::any storing a Z.
std::function<boost::any(std::vector<boost::any>)> erased_func_t;
template<class... Args, class F>
erased_func_t erase_func(F&& f) {
// TODO
}
Once we have written that, we can store a map from string to an erased_func_t for our table of functions.
We lookup the erased_func_t. We use the above deserialization infrastructure to generate a std::vector<boost::any> from the passed in parameters. We invoke it, having an exception thrown if it fails.
And bob is your uncle.
If you want to send the answer back, you'll need to type-erase going back to the wire format, and change erased_func_t to return the wire_data required to send it back over the wire instead of a boost::any. That would probably be best.
None of the above code has been tested. Some of it requires C++14 (not that much, mostly _t aliases), and some compilers who claim to support C++11 don't support the magic_switch implementation I wrote (it is almost pure C++11, except the _t aliases, I believe). But an equivalent one can be written, if more verbose.
Finally, like many things, it is usually not a good idea to write a RPC protocol from scratch. Odds are I missed an important step above.