Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Store functions with different signatures in a map

Tags:

I am trying to create a map with string as key and a generic method as value in C++, but I do not know if that is even possible. I would like to do something like that:

void foo(int x, int y) {    //do something }  void bar(std::string x, int y, int z) {    //do something }   void main() {    std::map<std::string, "Any Method"> map;     map["foo"] = &foo;      //store the methods in the map    map["bar"] = &bar;     map["foo"](1, 2);       //call them with parameters I get at runtime    map["bar"]("Hello", 1, 2); } 

Is that possible? If yes, how can I realise this?

like image 553
Ralf Hintersteininger Avatar asked Aug 16 '17 13:08

Ralf Hintersteininger


2 Answers

You can type-erase the function types into a container, then provide a template operator(). This will throw std::bad_any_cast if you get it wrong.

N.B. because of the type erasure, you will have to specify exactly matching arguments at the call site, as e.g. std::function<void(std::string)> is distinct from std::function<void(const char *)>, even though both can be called with a value like "Hello".

#include <any> #include <functional> #include <map> #include <string> #include <iostream>  template<typename Ret> struct AnyCallable {     AnyCallable() {}     template<typename F>     AnyCallable(F&& fun) : AnyCallable(std::function(std::forward<F>(fun))) {}     template<typename ... Args>     AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}     template<typename ... Args>     Ret operator()(Args&& ... args)      {          return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);      }     std::any m_any; };  void foo(int x, int y) {     std::cout << "foo" << x << y << std::endl; }  void bar(std::string x, int y, int z) {     std::cout << "bar" << x << y << z << std::endl; }   using namespace std::literals;  int main() {     std::map<std::string, AnyCallable<void>> map;          map["foo"] = &foo;      //store the methods in the map     map["bar"] = &bar;          map["foo"](1, 2);       //call them with parameters I get at runtime     map["bar"]("Hello, std::string literal"s, 1, 2);     try {         map["bar"]("Hello, const char *literal", 1, 2); // bad_any_cast     } catch (std::bad_any_cast&) {         std::cout << "mismatched argument types" << std::endl;     }     map["bar"].operator()<std::string, int, int>("Hello, const char *literal", 1, 2); // explicit template parameters          return 0; } 
like image 185
Caleth Avatar answered Sep 22 '22 09:09

Caleth


The most (I cannot say best here) you can do is to use a signature erasure. That mean to convert the pointer to functions to a common signature type, and then convert them back to the correct signature before using them.

That can only be done in very special use cases (I cannot imagine a real world one) and will be highly unsecure: nothing prevent you to pass the wrong parameters to a function. In short: NEVER DO THIS IN REAL WORLD CODE.

That being said, here is a working example:

#include <iostream> #include <string> #include <map>  typedef void (*voidfunc)();  void foo(int x, int y) {     std::cout << "foo " << x << " " << y << std::endl; }  void bar(std::string x, int y, int z) {     std::cout << "bar " << x << " " << y << " " << z << std::endl; }  int main() {     std::map<std::string, voidfunc> m;     m["foo"] = (voidfunc) &foo;     m["bar"] = (voidfunc)& bar;     ((void(*)(int, int)) m["foo"])(1, 2);     ((void(*)(std::string, int, int)) m["bar"])("baz", 1, 2);     return 0; } 

It gives as expected:

foo 1 2 bar baz 1 2 

I could not find in standard whether this invokes or not Undefined Behaviour because little is said about function pointer conversions, but I am pretty sure that all common compilers accept that, because it only involve function pointers casting.

like image 37
Serge Ballesta Avatar answered Sep 22 '22 09:09

Serge Ballesta