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?
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; }
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.
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