在地图中存储具有不同签名的函数

时间:2017-08-16 13:40:36

标签: c++

我正在尝试使用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);
}

这可能吗?如果是的话,我怎么能意识到这一点?

3 个答案:

答案 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>等容器中存储具有不同签名的函数。在这两种情况下,关于函数签名的信息都是唯一的。

valuemap的类型是1,这意味着存储在其中的对象所有相同的类型

因此,如果你的功能具有相同的签名,那么它很容易,否则,你必须放弃类型安全并开始走在一个非常危险的领域。 您删除类型信息关于存储在地图中的函数的那个​​。 这转换为map<string, void*>