如何在运行时恢复函数指针的类型

时间:2014-05-05 22:46:37

标签: c++ templates c++11 variadic-templates type-erasure

在代码中,我在manager类中注册一个或多个函数指针。

在这个类中,我有一个映射,将函数的参数类型映射到所述函数。它可能如下所示:std::map< std::vector<std::type_index> , void*>

template<typename Ret, typename... Args>
void Register(Ret(*function)(Args...)) {
    void* v = (void*)function;
    // recursively build type vector and add to the map
}

在运行时,代码使用任意数量的参数进行调用(来自外部脚本)。这些参数可以作为原始数据类型或将在编译时指定的自定义类型读取。


每次调用脚本时,我都要找出要调用的函数,然后调用它。前者很容易并且已经解决了(在循环中用type_index填充向量),但我想不出后者的解决方案。

我的第一种方法是在递归中使用可变参数模板,并为每种读取类型添加模板参数 - 但事实证明这是不可能的,因为模板是在编译时构造的,并且在运行时读取任意数量的参数。

然而,如果没有可变参数模板,我认为没有任何可能实现这一点。我考虑过boost::any而不是void*,但我没有看到这将如何解决回退到原始类型的需要。我还想过使用std::function,但这是一个模板化类型,因此无法将其存储在具有不同参数的函数的映射中。


(如果不清楚我在问什么,请考虑LuaBindsregister overloaded functions的可能性。我试着理解它是如何在那里实现的(没有可变参数模板,前C ++ 11 ),但无济于事。)

3 个答案:

答案 0 :(得分:3)

假设你有某种vector的参数和一个已知函数(完全)。

你可以这样打电话。调用执行此操作的函数invoke

接下来,了解如何为template<class... Args>执行此操作。增加invoke

所以你写了:

typedef std::vector<run_time_stuff> run_time_args;
template<class... Args>
void invoke( void(*func)(Args...), run_time_args rta )

此时此刻。请注意,我们知道参数的类型。我并不认为上述内容很容易写,但我相信你能搞清楚。

现在我们总结一下:

template<class...Args>
std::function<void(run_time_args)> make_invoker(void(*func)(Args...)){
  return [func](run_time_args rta){
    invoke(func, rta);
  };
}

现在代替void*存储std::function<void(run_time_args)> - 调用者。将函数指针添加到机制时,使用make_invoker而不是强制转换为void*

基本上,在我们有类型信息的地方,我们存储如何使用它。然后我们想要使用它,我们使用存储的代码!

invoke是另一个问题。它可能涉及索引技巧。

假设我们支持两种参数 - doubleint。然后,运行时的参数将作为std::vector< boost::variant<double, int> >加载到run_time_args

接下来,让我们扩展上面的invoke函数,以便在参数类型不匹配的情况下返回错误。

enum class invoke_result {
  everything_ok,
  error_parameter_count_mismatch,
  parameter_type_mismatch,
};
typedef boost::variant<int,double> c;
typedef std::vector<run_time_stuff> run_time_args;
template<class... Args>
invoke_result invoke( void(*func)(Args...), run_time_args rta );

现在有一些索引技巧的样板:

template<unsigned...Is>struct indexes{typedef indexes type;};
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1, Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
template<unsigned Max>using make_indexes_t=typename make_indexes<Max>::type;

有了这个,我们可以写一个调用者:

namespace helpers{
  template<unsigned...Is, class... Args>
  invoke_result invoke( indexes<Is...>, void(*func)(Args...), run_time_args rta ) {
    typedef void* pvoid;
    if (rta.size() < sizeof...(Is))
      return invoke_result::error_parameter_count_mismatch;
    pvoid check_array[] = { ((void*)boost::get<Args>( rta[Is] ))... };
    for( pvoid p : check_array )
      if (!p)
        return invoke_result::error_parameter_type_mismatch;
    func( (*boost::get<Args>(rts[Is]))... );
  }
}

template<class... Args>
invoke_result invoke( void(*func)(Args...), run_time_args rta ) {
  return helpers::invoke( make_indexes_t< sizeof...(Args) >{}, func, rta );
}

func的args与run_time_args内传递的args完全匹配时,这应该有用。

请注意,由于std::move std::vector未能{{1}},我速度快而且松散。而且上述内容并不支持隐式类型转换。而且我没有编译上面的任何代码,所以它可能充斥着错别字。

答案 1 :(得分:1)

几周前,我在使用可变参数模板,并想出了一个可能对你有帮助的解决方案。

DELEGATE.H

template <typename ReturnType, typename ...Args>
class BaseDelegate
{
public:
    BaseDelegate()
        : m_delegate(nullptr)
    {

    }

    virtual ReturnType Call(Args... args) = 0;
    BaseDelegate* m_delegate;
};

template <typename ReturnType = void, typename ...Args>
class Delegate : public BaseDelegate<ReturnType, Args...>
{
public:
    template <typename ClassType>
    class Callee : public BaseDelegate
    {
    public:
        typedef ReturnType (ClassType::*FncPtr)(Args...);
    public:
        Callee(ClassType* type, FncPtr function)
            : m_type(type)
            , m_function(function)
        {

        }

        ~Callee()
        {

        }

        ReturnType Call(Args... args)
        {
            return (m_type->*m_function)(args...);
        }

    protected:
        ClassType* m_type;
        FncPtr m_function;
    };

public:
    template<typename T>
    void RegisterCallback(T* type, ReturnType (T::*function)(Args...))
    {
        m_delegate = new Callee<T>(type, function);
    }

    ReturnType Call(Args... args)
    {
        return m_delegate->Call(args...);
    }
};

的main.cpp

class Foo
{
public:
    int Method(int iVal)
    {
        return iVal * 2;
    }
};

int main(int argc, const char* args)
{
    Foo foo;
    typedef Delegate<int, int> MyDelegate;

    MyDelegate m_delegate;
    m_delegate.RegisterCallback(&foo, &Foo::Method);

    int retVal = m_delegate.Call(10);

    return 0;
}

答案 2 :(得分:0)

不确定您的要求是否允许这样做,但您可以使用std::functionstd::bind

以下解决方案做出以下假设:

  • 您知道要调用的函数及其参数
  • 这些函数可以有任何签名和任意数量的参数
  • 您希望使用类型擦除来存储这些函数和参数,并在以后的所有时间调用它们

这是一个有效的例子:

#include <iostream>
#include <functional>
#include <list>

// list of all bound functions
std::list<std::function<void()>> funcs;

// add a function and its arguments to the list
template<typename Ret, typename... Args, typename... UArgs>
void Register(Ret(*Func)(Args...), UArgs... args)
{
    funcs.push_back(std::bind(Func, args...));
}

// call all the bound functions
void CallAll()
{
    for (auto& f : funcs)
        f();
}

////////////////////////////
// some example functions
////////////////////////////

void foo(int i, double d)
{
    std::cout << __func__ << "(" << i << ", " << d << ")" << std::endl;
}

void bar(int i, double d, char c, std::string s)
{
    std::cout << __func__ << "(" << i << ", " << d << ", " << c << ", " << s << ")" << std::endl;
}

int main()
{
    Register(&foo, 1, 2);
    Register(&bar, 7, 3.14, 'c', "Hello world");

    CallAll();
}