我正在尝试使用map
创建string
作为密钥,并在C ++中创建value
的通用方法,但我不知道这是否可行。我想做那样的事情:
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);
}
这可能吗?如果是的话,我怎么能意识到这一点?
答案 0 :(得分:16)
您可以将函数类型键入 - 删除到容器中,然后提供模板operator()
。如果你弄错了,这将抛出std::bad_any_cast
。
N.B。由于类型擦除,您必须在呼叫站点指定完全匹配的参数,例如, std::function<void(std::string)>
与std::function<void(const char *)>
不同,即使两者都可以使用"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(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;
};
template<>
struct AnyCallable<void>
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
void operator()(Args&& ... args)
{
std::invoke(std::any_cast<std::function<void(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);
//map["bar"]("Hello, const char *literal", 1, 2); // bad_any_cast
map["bar"].operator()<std::string, int, int>("Hello, const char *literal", 1, 2); // explicit template parameters
return 0;
}
答案 1 :(得分:6)
你能做的最多(我不能说最好)是使用签名擦除。这意味着将指向函数的指针转换为通用签名类型,然后在使用它们之前将它们转换回正确的签名。
这只能在非常特殊的用例中完成(我无法想象真实世界)并且会非常不安全:没有什么能阻止你将错误的参数传递给函数。简而言之:永远不要在现实世界中使用。
话虽如此,这是一个有效的例子:
#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;
}
它按预期给出:
foo 1 2
bar baz 1 2
我无法在标准中找到是否调用未定义的行为,因为关于函数指针转换的说法很少,但我很确定所有常见的编译器都接受它,因为它只涉及函数指针铸造。
答案 2 :(得分:1)
无论是将它们存储为函数指针还是map
,都无法在std ::function<WHATEVER>
等容器中存储具有不同签名的函数。在这两种情况下,关于函数签名的信息都是唯一的。
value
中map
的类型是1,这意味着存储在其中的对象所有相同的类型。
因此,如果你的功能具有相同的签名,那么它很容易,否则,你必须放弃类型安全并开始走在一个非常危险的领域。
您删除类型信息关于存储在地图中的函数的那个。
这转换为map<string, void*>
。