【问题标题】:Can I make a function table regardless of parameter signature?无论参数签名如何,我都可以制作函数表吗?
【发布时间】:2014-10-23 03:57:10
【问题描述】:

我想从一个表中调用所有类型的函数。 (考虑返回类型都是无效的)

为了说明我在说什么,这里有一些显然不起作用的代码。

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

void foo(int x){std::cout<<x;}
void bar(){std::cout<<"bar";}


std::map<std::string, std::function<void()>> map = 
{
    {"foo", foo},
    {"bar", bar}
};

int main()
{
    map["foo"](2);
    map["bar"]();
}

我不反对 C 风格的解决方案。

【问题讨论】:

  • 喜欢here ?
  • 你将如何调用这些函数?到时候你肯定会知道参数。在这种情况下,为什么不为每种类型的函数签名提供单独的映射。您可以使用模板类,其中函数类型是模板参数,其中包含静态映射。
  • @NeilKirk 我也在考虑这个问题。我正在为一种简单的语言编写临时解释器。这些值将来自用户。
  • 所以你有一个像"foo(2)"这样的文本字符串。你打算如何从中得到一行代码map["foo"](2);?先想清楚这部分。
  • 如果您正在编写解释器,则动态类型语言的适当签名可能更像函数<:any>)>,或者可能的函数(vector<:variant>>);如果它们具有不同的签名,那么调用你的函数将非常困难,除非你陷入 C 风格的可变参数的地狱。

标签: c++ c++11 lambda


【解决方案1】:

您可以将指针声明为旧式 C 函数指针 一个可变参数函数,例如:

 foo(...);
 bar(...);

 std::map<void(*)(...)> map = 
 {
    {"foo", foo},
    {"bar", bar}
 };

但是 foo 和 bar 必须遵循 va_args、va_start 的可变参数调用约定 等等,您只能从列表中拉出 C POD。不知道这是否值得麻烦。调用方法仍然必须知道要传递多少个参数。

看起来你可能会重新考虑你的设计。

例如,如果这应该是一种 CLI 的命令表,最好将std::vector&lt;std::string&gt; 传递给每个潜在命令,并使其确定向量是否具有正确的 size() 用于其目的.

【讨论】:

    【解决方案2】:

    如果您完全放弃类型系统,则可以使用boost::any,只要您在任何地方都能完全正确地获取所有类型。现在只适用于明确地将所有内容都设为 std::function 但我确信也有解决方法(更新为免费函数添加了重载):

    class Functions
    {
    public:
        template <typename... T>
        void add_function(const std::string& name, void (*f)(T...))
        {   
            fs[name] = std::function<void(T...)>{f};
        }   
    
    
        template <typename... T>
        void add_function(const std::string& name, std::function<void(T...)> f)
        {   
            fs[name] = f;
        }   
    
        template <typename... T>
        void call(const std::string& name, T... args)
        {   
            auto it = fs.find(name);
            if (it != fs.end()) {
                auto f = boost::any_cast<std::function<void(T...)>>(&it->second);
                if (f) {
                    (*f)(args...);
                }   
                else {
                    std::cout << "invalid args for " << name << std::endl;
                }
            }   
            else {
                std::cout << "not found: " << name << std::endl;
            }   
        }   
    
    private:
        std::map<std::string, boost::any> fs; 
    };
    
    void baz() {
        std::cout << "baz" << std::endl;
    }
    
    int main() {
        std::function<void()> foo = []{ std::cout << "foo" << std::endl; };
        std::function<void(int)> bar = [](int i){ std::cout << "bar(" << i << ")" << std::endl;     
    };
    
        Functions f;
        f.add_function("foo", foo );
        f.add_function("bar", bar);
        f.add_function("baz", baz);
    
        f.call("foo");
        f.call("bar", 42);
        f.call("baz");
    }
    

    功能,是的。好主意?另请注意,f.call("bar", 42u) 将失败,因为您必须完全正确地获取每种类型。

    【讨论】:

      【解决方案3】:

      我稍微改变了你的方法,请注意这只是一个示例,我很确定它不会像这样编译,但它会让你了解我的想法。 您可以在另一个结构中注册您的函数,然后调用适当的结构,转发参数。

      struct Funcs
      {
          std::function<void(int)> _f1;
          std::function<void()>    _f2;
      
          template<typename args...>
          void call(std::string&& f_name, args...)
          {
              if(f_name == "foo")
                _f1(std::forward(args)...)
              if(f_name == "bar")
                _f2(std::forward(args)...)
          }
      }
      
      
      int main()
      {
          Funcs f;
          f.call("foo", 2);
      }
      

      【讨论】:

        【解决方案4】:

        如果你真的想存储任何函数,并且总能弄清楚如何正确调用它,你可以扩展 Oncaphillis 的方法,然后继续转换函数指针:

        void foo(int);
        float bar(double, struct baz);
        
        std::map<void(*)()> map = {
            {"foo", (void(*)())foo},
            {"bar", (void(*)())bar}
        };
        

        然后你可以在使用它们时将它们回退:

        //code to make sure that map["foo"] is of type `void(*)(int)`
        (*(void(*)(int))map["foo"])(42);
        
        //code to make sure that map["bar"] is of type `float(*)(double, struct baz)`
        float result = (*(float(*)(double, struct baz))map["foo"])(3.14159, (struct baz){ /*whatever*/});
        

        如您所见,以这种方式调用任何类型的函数都没有问题,而不仅限于可变参数。但是,这种方法很容易出错,因为您完全取消了类型系统提供的安全性,并且您的强制转换必须 100% 正确。如果你不这样做,可能会发生奇怪的事情。这与使用boost::any 的问题相同。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-09-29
          • 2017-01-02
          • 1970-01-01
          • 2013-10-26
          • 2023-01-31
          相关资源
          最近更新 更多