I have a collection of delegates using std::function
that point to functions with different signatures. I want to be able to retrieve those delegates at run time using string keys. I can't seem to use a map because they point to functions with different signatures. Can I have such functionality without a switch statement?
For example, a real world use case is an RPC system. Do they really just make the methods have the same signature or use code generation?
E:useful links related to the selected answer which will take a fair amount of time to grasp
http://en.cppreference.com/w/cpp/utility/functional/function
http://en.cppreference.com/w/cpp/utility/forward
http://en.cppreference.com/w/cpp/utility/integer_sequence
http://en.cppreference.com/w/cpp/types/result_of
http://en.cppreference.com/w/cpp/types/remove_reference
http://www.boost.org/doc/libs/1_57_0/doc/html/any.html
The method signature in java is defined as the structure of the method that is designed by the programmer. The method signature is the combination of the method name and the parameter list. The method signature depicts the behavior of the method i.e types of values of the method, return type of the method, etc.
Method Signature According to Oracle, the method signature is comprised of the name and parameter types. Therefore, all the other elements of the method's declaration, such as modifiers, return type, parameter names, exception list, and body are not part of the signature.
The signature of a method consists of the name of the method and the description (i.e., type, number, and position) of its parameters. Example: toUpperCase() println(String s)
Method Calls A method is a routine that applies to a particular class of objects. Once an object is declared, you can refer to it by its identifier when calling methods.
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.
The problem with lookup tables and std::map
is that all the function pointers must have the same signature.
However, there is a complex solution around this. It involves function objects (functors) and base classes.
Let us define the base functor class:
struct Base_Parameters;
struct Base_Functor
{
virtual void execute(Base_Parameter * p_parameters) = 0;
};
This solves the issue of how to declare the std::map
:
typedef std::map<std::string, Base_Functor *> Function_Key_Container;
Any function would be derived from the base function structure: struct My_Functor : Base_Functor { void execute(Base_Parameters * p_parameters) { //... } };
You would add the function to the map, something like this:
Function_Key_Container function_map;
function_map["Apple"] = new My_Functor;
The issue now is passing parameters to the function.
I haven't fully worked this one out yet, but here are some suggestions:
Base_Parameters
and in the function object, use
dynamic_cast
to convert the Base_Parameters
pointer to a pointer
to the child parameters.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With