Store Functions with Different Signatures in a Map

Store functions with different signatures in a map

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

How to use one map to store different function

std::any will be your friend. Together with some wrapper class and a template function within the wrapper class, to hide the any cast, it will be some how more intuitive.

And it will give you additional possibilities.

Please see:

#include <iostream>
#include <map>
#include <string>
#include <any>
#include <utility>

class Caller
{
std::map<int, std::any> selector;
public:
Caller() : selector() {}

Caller(std::initializer_list<std::pair<const int, std::any>> il) : selector(il) {}
template<typename Function>
void add(int key, Function&& someFunction) { selector[key] = std::any(someFunction); };

template <typename ... Args>
void call(int key, Args ... args) {
if (selector.find(key) != selector.end()) {
std::any_cast<std::add_pointer_t<void(Args ...)>>(selector[key])(args...);
}
}
};

// Some demo functions
void a(int x) {
std::cout << "a\t" << x << '\n';
};
void b(int x, int y) {
std::cout << "b\t" << x << '\t' << y << '\n';
};
void c(int x, int y, std::string z) {
std::cout << "c\t" << x << '\t' << y << '\t' << z << '\n';
};
void d(std::string s, int x, int y, int z) {
std::cout << "d\t" << s << '\t' << x << '\t' << y << '\t' << z << '\n';
};


// Definition of our caller map (using initializer list)
Caller caller{
{1, a},
{2, b},
{3, c} };

int main() {

// Some demo calls
caller.call(1, 1);
caller.call(2, 1, 2);
caller.call(3, 1, 2, std::string("3"));

// Adding an additional function
caller.add(4, d);

// And call this as well.
caller.call(4, std::string("ddd"), 1,2,3);
return 0;
}

How to store functional objects with different signatures in a container?


#include <functional>
#include <iostream>
#include <string>
#include <map>

class api {
// maps containing the different function pointers
typedef void(*voidfuncptr)();
typedef int(*stringcrintptr)(std::string, const int&);

std::map<std::string, voidfuncptr> voida;
std::map<std::string, stringcrintptr> stringcrint;
public:
// api temp class
// given an api and a name, it converts to a function pointer
// depending on parameters used
class apitemp {
const std::string n;
const api* p;
public:
apitemp(const std::string& name, const api* parent)
: n(name), p(parent) {}
operator voidfuncptr()
{ return p->voida.find(n)->second; }
operator stringcrintptr()
{ return p->stringcrint.find(n)->second; }
};

// insertion of new functions into appropriate maps
void insert(const std::string& name, voidfuncptr ptr)
{ voida[name]=ptr; }
void insert(const std::string& name, stringcrintptr ptr)
{ stringcrint[name]=ptr; }
// operator[] for the name gets halfway to the right function
apitemp operator[](std::string n) const
{ return apitemp(n, this); }
};

Usage:

api myMap; 

int hello_world(std::string name, const int & number )
{
name += "!";
std::cout << "Hello, " << name << std::endl;
return number;
}

int main()
{
myMap.insert("my_method_hello", &hello_world );
int a = myMap["my_method_hello"]("Tim", 25);
}

Not very pretty. Better advice is to not do anything even remotely like whatever it is you're trying to do.

Note that this requires all functions with the same parameters to return the same type.

How to map different function signatures into the same std::map?

If I understand the comments correctly, the std::map<Key, Value> is only part of the problem. You first need the Value part - what C++ type can hold the overload set of f?

Jarod's comment is right: You basically need [](auto... args){ return foo(args...)};. This is a single lambda object, with an overloaded operator(). Each overload of operator() selects one of your foo() overloads.

This then shows the ultimate problem. [](auto... args){ return bar(args...)}; is another lambda, with a different type, so it can't go in the same std::map<Key, Value>.

That's really no surprise. When the compiler sees baz["foo"](qux), it needs Value::operator(std::string). That can be a template instantiation, but an instantiation cannot happen at runtime. So Value can't depend on "foo". C++ simply does not have run-time overloading, that is not how the language works.

@JesperJuhl may have a point that this is an XY problem. What if you had foo(std::variant<int, std::string, std::vector<int>>) ? This is not an overload set; this is a single function. Behind the scenes, it can dispatch to foo_impl(...) in any way you like.

How to store functional objects with different signatures in modern C++

You can write your own any. Without all the compiler workarounds and stuff, boost::any can be written in about 30 lines of code.



Related Topics



Leave a reply



Submit