Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling methods of different signatures by name

Tags:

c++

c++11

c++14

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

like image 697
FPGA Avatar asked Mar 09 '15 23:03

FPGA


People also ask

What are method signatures?

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.

Which part of the method is called signature?

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.

Which is a method signature example?

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)

What is a method call?

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.


2 Answers

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.

like image 75
Yakk - Adam Nevraumont Avatar answered Oct 06 '22 01:10

Yakk - Adam Nevraumont


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:

  • Use boost::variant -- a variant record.
  • Create a child from Base_Parameters and in the function object, use dynamic_cast to convert the Base_Parameters pointer to a pointer to the child parameters.
like image 22
Thomas Matthews Avatar answered Oct 06 '22 00:10

Thomas Matthews