在运行时动态创建C ++函数参数列表

Dynamically creating a C++ function argument list at runtime

我试图在运行时生成函数调用的参数列表,但我想不出在C++中实现这一点的方法。

这是给我正在写的助手库的。我通过网络从客户机获取输入数据,并使用该数据调用用户先前设置的函数指针。该函数接受一个字符串(标记,类似于printf)和不同数量的参数。我需要的是根据从客户机接收到的数据添加更多参数的方法。

我将函数存储在函数指针的映射中

1
2
typedef void (*varying_args_fp)(string,...);
map<string,varying_args_fp> func_map;

一个示例用法是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void printall(string tokens, ...)
{
    va_list a_list;
    va_start(a_list, tokens);

    for each(auto x in tokens)
    {
        if (x == 'i')
        {
            cout <<"Int:" << va_arg(a_list, int) << ' ';
        }
        else if(x == 'c')
        {
            cout <<"Char:" << va_arg(a_list, char) << ' ';
        }
    }

    va_end(a_list);
}

func_map["printall"] = printall;
func_map["printall"]("iic",5,10,'x');
// prints"Int: 5 Int: 10 Char: x"

当硬编码函数调用和它的参数时,这很好地工作,但是如果我已经收到数据"createx 10 20",程序需要能够使参数调用本身。如

1
2
// func_name ="CreateX", tokens = 'ii', first_arg = 10, second_arg = 20
func_map[func_name](tokens,first_arg,second_arg);

我无法预测用户将如何布置函数并事先对此进行编码。

如果有人对以其他方式完成这项任务有建议,请随时提出建议。我需要用户能够将一个函数"绑定"到库中,并且库在从联网客户机接收到数据后稍后调用它,这本质上是一个回调。


这里是一个C++ 11解决方案。它不支持像printallprintf这样的varargs函数,使用这种技术是不可能的,而imo根本不可能,或者至少非常复杂。无论如何,在像您这样的环境中很难安全地使用这种功能,因为来自任何客户机的任何错误请求都可能导致服务器崩溃,完全没有任何追索权。为了更好的安全性和稳定性,您可能应该转向基于容器的接口。

另一方面,此方法支持所有(?)其他功能统一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <vector>
#include <iostream>
#include <functional>
#include <stdexcept>
#include <string>
#include <boost/any.hpp>


template <typename Ret, typename... Args>
Ret callfunc (std::function<Ret(Args...)> func, std::vector<boost::any> anyargs);

template <typename Ret>
Ret callfunc (std::function<Ret()> func, std::vector<boost::any> anyargs)
{
    if (anyargs.size() > 0)
        throw std::runtime_error("oops, argument list too long");
    return func();
}

template <typename Ret, typename Arg0, typename... Args>
Ret callfunc (std::function<Ret(Arg0, Args...)> func, std::vector<boost::any> anyargs)
{
    if (anyargs.size() == 0)
        throw std::runtime_error("oops, argument list too short");
    Arg0 arg0 = boost::any_cast<Arg0>(anyargs[0]);
    anyargs.erase(anyargs.begin());
    std::function<Ret(Args... args)> lambda =
        ([=](Args... args) -> Ret {
         return func(arg0, args...);
    });
    return callfunc (lambda, anyargs);
}

template <typename Ret, typename... Args>
std::function<boost::any(std::vector<boost::any>)> adaptfunc (Ret (*func)(Args...)) {
    std::function<Ret(Args...)> stdfunc = func;
    std::function<boost::any(std::vector<boost::any>)> result =
        ([=](std::vector<boost::any> anyargs) -> boost::any {
         return boost::any(callfunc(stdfunc, anyargs));
         });
    return result;
}

基本上,您称为adaptfunc(your_function),其中your_function是任何类型的函数(varargs除外)。作为回报,您得到一个接受boost::any矢量并返回boost::any矢量的std::function对象。你把这个东西放在你的电脑里,或者做你想做的任何事情。

实际调用时检查参数类型及其编号。

不支持返回void的函数,因为不支持boost::any。这可以通过将返回类型包装在一个简单的模板中并专门针对void来轻松处理。为了清楚起见,我把它忘了。

这是一个测试驱动程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int func1 (int a)
{
    std::cout <<"func1(" << a <<") =";
    return 33;
}

int func2 (double a, std::string b)
{
    std::cout <<"func2(" << a <<","" << b <<"") =";
    return 7;
}

int func3 (std::string a, double b)
{
    std::cout <<"func3(" << a <<","" << b <<"") =";
    return 7;
}

int func4 (int a, int b)
{
    std::cout <<"func4(" << a <<"," << b <<") =";
    return a+b;
}


int main ()
{
    std::vector<std::function<boost::any(std::vector<boost::any>)>> fcs = {
        adaptfunc(func1), adaptfunc(func2), adaptfunc(func3), adaptfunc(func4) };

    std::vector<std::vector<boost::any>> args =
    {{777}, {66.6, std::string("yeah right")}, {std::string("whatever"), 0.123}, {3, 2}};

    // correct calls will succeed
    for (int i = 0; i < fcs.size(); ++i)
        std::cout << boost::any_cast<int>(fcs[i](args[i])) << std::endl;

    // incorrect calls will throw
    for (int i = 0; i < fcs.size(); ++i)
        try {
            std::cout << boost::any_cast<int>(fcs[i](args[fcs.size()-1-i])) << std::endl;
        } catch (std::exception& e) {
            std::cout <<"Could not call, got exception:" << e.what() << std::endl;
        }
}


正如@tonythelion已经提到的,您可以使用boost::variantboost::any在运行时在类型之间进行选择:

1
2
typedef std::function<void(const std::string&, const std::vector<boost::variant<char, int>>&)> varying_args_fn;
std::map<std::string, varying_args_fn> func_map;

您可以使用静态访问者来区分类型。下面是一个完整的示例,请注意,实际上不再需要tokens参数,因为boost::variant在运行时知道存储在其中的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <map>
#include <vector>
#include <string>
#include <functional>
#include <iostream>

#include <boost/variant.hpp>
#include <boost/any.hpp>

typedef std::function<void(const std::string&, const std::vector<boost::variant<char, int>>&)> varying_args_fn;

void printall(const std::string& tokens, const std::vector<boost::variant<char, int>>& args) {
  for (const auto& x : args) {
    struct : boost::static_visitor<> {
      void operator()(int i) {
        std::cout <<"Int:" << i << ' ';
      }
      void operator()(char c) {
        std::cout <<"Char:" << c << ' ';
      }
    } visitor;
    boost::apply_visitor(visitor, x);
  }
}

int main() {
  std::map<std::string, varying_args_fn> func_map;
  func_map["printall"] = printall;
  func_map["printall"]("iic", {5, 10, 'x'});
}