从指针映射调用函数,该函数具有可变数字参数的函数

时间:2014-01-10 16:38:17

标签: c++ c++11

考虑以下计划。有没有办法在没有正文中所有call()语句的情况下实现if函数?随意更改地图类型以找到解决方案。如果参数数量不正确,call()也应该抛出异常。 call()的接口可以更改,但函数的名称,参数数组的指针和参数的数量仅在运行时才知道。

#include <iostream>
#include <string>
#include <map>
#include <cmath>
#include <boost/any.hpp>

using namespace std;

typedef double(*PF1)(double);
typedef double(*PF2)(double, double);
typedef double(*PF3)(double, double, double);

map<string, boost::any> m = {
    {"sin", static_cast<PF1> (std::sin)},
    {"pow", static_cast<PF2> (std::pow)}
    // other 
};

double call(string name, double* args, int nargs) {
    if (name == "sin" && nargs == 1)
        return boost::any_cast<PF1>(m[name])(args[0]);
    else if (name == "pow" && nargs == 2)
        return boost::any_cast<PF2>(m[name])(args[0], args[1]);
    // etc...
}

int main() {
    double n[] = {1, 2, 3, 4, 5, 6};
    int narg1 = 1, narg2 = 2; // known at runtime
    double r = call("sin", n, narg1);
    r = call("pow", n, narg2);
}

2 个答案:

答案 0 :(得分:1)

这是一种方法:使用继承进行类型擦除。这是一个将函数指针存储为非类型模板参数的最小示例。

另一种方法是使用std::function,它会丢弃其他参数IIRC。

struct var_double_func_base
{
    virtual ~var_double_func_base() {}
    virtual double operator()(double*, int) = 0;
};


// a helper type to generate and deduce a sequence of integers
template<int...> struct seq {};
template<int N, int... Is> struct gen_seq : gen_seq<N-1, N-1, Is...> {};
template<int... Is> struct gen_seq<0, Is...> : seq<Is...> {};


#include <cassert>

// a delegate-like class, storing the function to call as a non-type template
// parameter
template<class T, T t>
struct var_double_fptr;

template<class... Args, double(*fptr)(Args...)>
struct var_double_fptr<double(*)(Args...), fptr>
    : var_double_func_base
{
    virtual double operator()(double* argv, int argc)
    {
        assert(argc == sizeof...(Args));
        return dispatch(argv, gen_seq<sizeof...(Args)>{});
    }

    template<int... Is>
    double dispatch(double* argv, seq<Is...>)
    {
        return fptr(argv[Is]...);
    }
};

用法示例:(注意 - 更喜欢unique_ptr,但这不适用于地图的统一初始化。)

#include <memory>
#include <map>
#include <string>
#include <cmath>

typedef double(*PF1)(double);
typedef double(*PF2)(double, double);
typedef double(*PF3)(double, double, double);

std::map<std::string, std::shared_ptr<var_double_func_base>> m = {
    {"sin", std::make_shared<var_double_fptr<PF1, std::sin>>()},
    {"pow", std::make_shared<var_double_fptr<PF2, std::pow>>()}
    // other 
};

double call(std::string name, double* argv, int argc)
{
    auto i = m.find(name);
    assert(i != m.end());
    return (*(i->second))(argv, argc);
}

int main() {
    double n[] = {1, 2, 3, 4, 5, 6};
    int narg1 = 1, narg2 = 2; // known at runtime
    double r = call("sin", n, narg1);
    r = call("pow", n, narg2);
}

答案 1 :(得分:1)

这是另一种方式。本质上,它使用OP的代码(通过if调度),因此它不需要映射或类型擦除。但是,它需要一个尾部重复的if - else s序列,而不是地图中的查找。所以查找速度较慢,但​​不需要间接。

// a helper type to generate and deduce a sequence of integers
template<int...> struct seq {};
template<int N, int... Is> struct gen_seq : gen_seq<N-1, N-1, Is...> {};
template<int... Is> struct gen_seq<0, Is...> : seq<Is...> {};


#include <string>
#include <cassert>

template<class T, T t>
struct c_fptr;

template<class... Args, double(*fptr)(Args...)>
struct c_fptr<double(*)(Args...), fptr>
{
    std::string name;

    double operator()(double* argv, int argc) const
    {
        assert(argc == sizeof...(Args));
        return dispatch(argv, gen_seq<sizeof...(Args)>{});
    }

    template<int... Is>
    double dispatch(double* argv, seq<Is...>) const
    {
        return fptr(argv[Is]...);
    }
};

与我的类型擦除方法类似,我们使用模板参数来存储函数指针。此类型的对象将存储在tuple

#include <map>
#include <cmath>

typedef double(*PF1)(double);
typedef double(*PF2)(double, double);
typedef double(*PF3)(double, double, double);

#include <tuple>

auto x = std::make_tuple(
      c_fptr<PF1, std::sin>{"sin"}
    , c_fptr<PF2, std::pow>{"pow"}
);

此元组现在用于递归函数调用,其中每个步骤检查其中一个元组元素的名称是否与传递的字符串匹配:

// end recursion
double call_recurse(std::string const& name, double* argv, int argc)
{
    throw std::invalid_argument("name not found");
}

template<class T, class... TT>
double call_recurse(std::string const& name, double* argv, int argc,
                    T const& l, TT const&... rest)
{
    if(name == l.name)
    {
        return l(argv, argc);
    }else
    {
        return call_recurse(name, argv, argc, rest...);
    }
}

template<int... Is>
double call_dispatch(std::string const& name, double* argv, int argc, seq<Is...>)
{
    return call_recurse(name, argv, argc, std::get<Is>(x)...);
}

double call(std::string const& name, double* argv, int argc)
{
    return call_dispatch(name, argv, argc,
                         gen_seq<std::tuple_size<decltype(x)>::value>{});
}