简体   繁体   English

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

[英]Store functions with different signatures in a map

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. 我正在尝试使用string作为键创建一个map ,并使用泛型方法作为C ++中的value ,但我不知道是否可能。 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() . 您可以将函数类型键入 - 擦除到容器中,然后提供模板operator() This will throw std::bad_any_cast if you get it wrong. 如果你弄错了,这将抛出std::bad_any_cast

NB because of the type erasure, you will have to specify exactly matching arguments at the call site, as eg std::function<void(std::string)> is distinct from std::function<void(const char *)> , even though both can be called with a value like "Hello" . NB因为类型擦除,你必须在调用站点指定完全匹配的参数,例如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;
}

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. 我无法在标准中找到是否调用未定义的行为,因为关于函数指针转换的说法很少,但我很确定所有常见的编译器都接受它,因为它只涉及函数指针转换。

You cannot store functions with different signatures in a container like map , no matter if you store them as a function pointer or std ::function<WHATEVER> . 无论是将它们存储为函数指针还是std ::function<WHATEVER> ,都不能将具有不同签名的函数存储在像map这样的容器中。 The information about the signature of the function is one and only one in both cases. 在两种情况下,关于函数签名的信息是唯一的。

The types for the value in map is one, meaning that the object stored in it are all of the same type . map value的类型是1,这意味着存储在其中的对象都是相同的类型

So if your functions have all the same signature, then it's easy, otherwise, you have to abandon type safety and start walking in a very dangerous realm. 因此,如果您的功能具有相同的签名,那么它很容易,否则,您必须放弃类型安全并开始走在一个非常危险的领域。 The one in which you erase the type information about the functions stored inside the map. 删除有关存储在地图中的函数的类型信息的那个。 This translates to something like map<string, void*> . 这转换为map<string, void*>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM