What is a good way to register functions for a dynamic call in C ++?

In my current setup, I have

typedef std::function<void (MyClass&, std::vector<std::string>) MyFunction; std::map<std::string, MyFunction> dispatch_map; 

And I register my functions in it using a macro. However, I have a problem with this: the parameters are passed as a vector of strings that I have to convert inside the functions. I would prefer to do this conversion outside functions at the dispatcher level. Is it possible? Function signatures are known at compile time and never change at runtime.

+4
source share
5 answers

You can get pretty far with variadic templates and some templates / virtual methods. With the following codes you can do something like:

 std::string select_string (bool cond, std::string a, std::string b) { return cond ? a : b; } int main () { Registry reg; reg.set ("select_it", select_string); reg.invoke ("select_it", "1 John Wayne")); reg.invoke ("select_it", "0 John Wayne")); } 

exit:

 John Wayne 

Full implementation:

These codes are exemplary. You must optimize it to ensure perfect forwarding of less redundancy while expanding the list of options.

Headers and test function

 #include <functional> #include <string> #include <sstream> #include <istream> #include <iostream> #include <tuple> std::string select_string (bool cond, std::string a, std::string b) { return cond ? a : b; } 

This helps us parse the string and put the results in a tuple:

 //---------------------------------------------------------------------------------- template <typename Tuple, int Curr, int Max> struct init_args_helper; template <typename Tuple, int Max> struct init_args_helper<Tuple, Max, Max> { void operator() (Tuple &, std::istream &) {} }; template <typename Tuple, int Curr, int Max> struct init_args_helper { void operator() (Tuple &tup, std::istream &is) { is >> std::get<Curr>(tup); return init_args_helper<Tuple, Curr+1, Max>() (tup, is); } }; template <int Max, typename Tuple> void init_args (Tuple &tup, std::istream &ss) { init_args_helper<Tuple, 0, Max>() (tup, ss); } 

This expands the function pointer and tuple in the function call (by function pointer):

 //---------------------------------------------------------------------------------- template <int ParamIndex, int Max, typename Ret, typename ...Args> struct unfold_helper; template <int Max, typename Ret, typename ...Args> struct unfold_helper<Max, Max, Ret, Args...> { template <typename Tuple, typename ...Params> Ret unfold (Ret (*fun) (Args...), Tuple tup, Params ...params) { return fun (params...); } }; template <int ParamIndex, int Max, typename Ret, typename ...Args> struct unfold_helper { template <typename Tuple, typename ...Params> Ret unfold (Ret (*fun) (Args...), Tuple tup, Params ...params) { return unfold_helper<ParamIndex+1, Max, Ret, Args...> (). unfold(fun, tup, params..., std::get<ParamIndex>(tup)); } }; template <typename Ret, typename ...Args> Ret unfold (Ret (*fun) (Args...), std::tuple<Args...> tup) { return unfold_helper<0, sizeof...(Args), Ret, Args...> ().unfold(fun, tup); } 

This function combines:

 //---------------------------------------------------------------------------------- template <typename Ret, typename ...Args> Ret foo (Ret (*fun) (Args...), std::string mayhem) { // Use a stringstream for trivial parsing. std::istringstream ss; ss.str (mayhem); // Use a tuple to store our parameters somewhere. // We could later get some more performance by combining the parsing // and the calling. std::tuple<Args...> params; init_args<sizeof...(Args)> (params, ss); // This demondstrates expanding the tuple to full parameter lists. return unfold<Ret> (fun, params); } 

Here is our test:

 int main () { std::cout << foo (select_string, "0 John Wayne") << '\n'; std::cout << foo (select_string, "1 John Wayne") << '\n'; } 

Warning: the code needs additional verification during parsing and should use std::function<> instead of a bare function pointer


Based on the above code, just write a registry function:

 class FunMeta { public: virtual ~FunMeta () {} virtual boost::any call (std::string args) const = 0; }; template <typename Ret, typename ...Args> class ConcreteFunMeta : public FunMeta { public: ConcreteFunMeta (Ret (*fun) (Args...)) : fun(fun) {} boost::any call (std::string args) const { // Use a stringstream for trivial parsing. std::istringstream ss; ss.str (args); // Use a tuple to store our parameters somewhere. // We could later get some more performance by combining the parsing // and the calling. std::tuple<Args...> params; init_args<sizeof...(Args)> (params, ss); // This demondstrates expanding the tuple to full parameter lists. return unfold<Ret> (fun, params); } private: Ret (*fun) (Args...); }; class Registry { public: template <typename Ret, typename ...Args> void set (std::string name, Ret (*fun) (Args...)) { funs[name].reset (new ConcreteFunMeta<Ret, Args...> (fun)); } boost::any invoke (std::string name, std::string args) const { const auto it = funs.find (name); if (it == funs.end()) throw std::runtime_error ("meh"); return it->second->call (args); } private: // You could use a multimap to support function overloading. std::map<std::string, std::shared_ptr<FunMeta>> funs; }; 

You might even think about supporting function overloading with this, using multimap and dispatching solutions based on what content the arguments passed in are.

Here's how to use it:

 int main () { Registry reg; reg.set ("select_it", select_string); std::cout << boost::any_cast<std::string> (reg.invoke ("select_it", "0 John Wayne")) << '\n' << boost::any_cast<std::string> (reg.invoke ("select_it", "1 John Wayne")) << '\n'; } 
+1
source

If you can use boost, then here is an example of what I think you are trying to do (although it can work with std , I personally stick with boost):

 typedef boost::function<void ( MyClass&, const std::vector<std::string>& ) MyFunction; std::map<std::string, MyFunction> dispatch_map; namespace phx = boost::phoenix; namespace an = boost::phoenix::arg_names; dispatch_map.insert( std::make_pair( "someKey", phx::bind( &MyClass::CallBack, an::_1, phx::bind( &boost::lexical_cast< int, std::string >, phx::at( an::_2, 0 ) ) ) ) ); dispatch_map["someKey"]( someClass, std::vector< std::string >() ); 

However, since this type of nesting quickly becomes quite unreadable, it is usually best to create a helper (a free function or, even better, a lazy function) that performs the conversion.

+1
source

If you understand correctly, you want to register void MyClass::Foo(int) and void MyClass::Bar(float) , assuming that it will be executed from std::string to int or float .

To do this, you need a helper class:

 class Argument { std::string s; Argument(std::string const& s) : s(s) { } template<typename T> operator T { return boost::lexical_cast<T>(s); } }; 

This allows you to wrap both void MyClass::Foo(int) and void MyClass::Bar(float) in std::function<void(MyClass, Argument))> .

0
source

An interesting problem. This is not trivial in C ++, I wrote a standalone implementation in C ++ 11. In C ++ 03 you can do the same, but the code will be (even) less readable.

 #include <iostream> #include <sstream> #include <string> #include <functional> #include <vector> #include <cassert> #include <map> using namespace std; // string to target type conversion. Can replace with boost::lexical_cast. template<class T> T fromString(const string& str) { stringstream s(str); T r; s >> r; return r; } // recursive construction of function call with converted arguments template<class... Types> struct Rec; template<> struct Rec<> { // no parameters template<class F> static void call (const F& f, const vector<string>&, int) { f(); } }; template<class Type> struct Rec< Type > { // one parameter template<class F> static void call (const F& f, const vector<string>& arg, int index) { f(fromString<Type>(arg[index])); } }; template<class FirstType, class... NextTypes> struct Rec< FirstType, NextTypes... > { // many parameters template<class F> static void call (const F& f, const vector<string>& arg, int index) { Rec<NextTypes...>::call( bind1st(f, fromString<FirstType>(arg[index])), // convert 1st param arg, index + 1 ); } }; template<class... Types> void call // std::function call with strings (const function<void(Types...)>& f, const vector<string>& args) { assert(args.size() == sizeof...(Types)); Rec<Types...>::call(f, args, 0); } template<class... Types> void call // c function call with strings (void (*f)(Types...), const vector<string>& args) { call(function<void(Types...)>(f), args); } // transformas arbitrary function to take strings parameters template<class F> function<void(const vector<string>&)> wrap(const F& f) { return [&] (const vector<string>& args) -> void { call(f, args); }; } // the dynamic dispatch table and registration routines map<string, function<void(const vector<string>&)> > table; template<class F> void registerFunc(const string& name, const F& f) { table.insert(make_pair(name, wrap(f))); } #define smartRegister(F) registerFunc(#F, F) // some dummy functions void f(int x, float y) { cout << "f: " << x << ", " << y << endl; } void g(float x) { cout << "g: " << x << endl; } // demo to show it all works;) int main() { smartRegister(f); smartRegister(g); table["f"]({"1", "2.0"}); return 0; } 

Also, for characteristics, it is better to use unordered_map instead of a map and possibly avoid the overhead of std :: function if you only have regular C functions. Of course, this only makes sense if the sending time is significantly different from the execution time of the functions .

0
source

No, C ++ does not provide any options for this.

-2
source

All Articles