Pergunta

In the code I register one or multiple function pointer in a manager class.

In this class I have a map that maps the argument types of the function to said function. It may look like so: 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
}

At runtime the code gets calls (from an external script) with an arbitrary number of arguments. These arguments can be read as primitive data types or as custom types that will be specified at compile time.


With every call from the script, I have to find out which function to call, and then call it. The former is easy and already solved (filling a vector with type_index in a loop), but I can't think of a solution for the latter.

My first approach was using variadic templates in recursion with an added template argument for each read type - but this turned out to be impossible since templates are constructed at compile time, and the arbitrary number of arguments is read at runtime.

Without variadic templates however, I don't see any possibility to achieve this. I considered boost::any instead of void*, but I didn't see how that would solve the need to cast back to the original type. I also thought of using std::function but that would be a templated type, so it could not be stored in a map for functions with different arguments.


(If it's unclear what I'm asking, think of LuaBinds possibility to register overloaded functions. I tried to understand how it's implemented there (without variadic templates, pre-C++11), but to no avail.)

Foi útil?

Solução

Suppose you had the arguments in a vector of some kind, and a known function (fully).

You can call this. Call the function that does this invoke.

Next, work out how to do this for template<class... Args>. Augment invoke.

So you have written:

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

at this point. Note that we know the types of the argument. I do not claim the above is easy to write, but I have faith you can figure it out.

Now we wrap things up:

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

and now instead of void* you store std::function<void(run_time_args)> -- invokers. When you add the function pointers to the mechanism you use make_invoker instead of casting to void*.

Basically, at the point where we have the type info, we store how to use it. Then where we want to use it, we use the stored code!

Writing invoke is another problem. It will probably involve the indexes trick.

Suppose we support two kinds of arguments -- double and int. The arguments at run time are then loaded into a std::vector< boost::variant<double, int> > as our run_time_args.

Next, let us extend the above invoke function to return an error in the case of parameter type mismatch.

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 );

now some boilerplate for the indexes trick:

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;

With that, we can write an invoker:

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 );
}

And that should work when func's args exactly match the ones passed in inside run_time_args.

Note that I was fast and loose with failing to std::move that std::vector around. And that the above doesn't support implicit type conversion. And I didn't compile any of the above code, so it is probably littered with typos.

Outras dicas

I was messing around with variadic templates a few weeks ago and came up with a solution that might help you.

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;
}

Not sure if your requirements will allow this, but you could possibly just use std::function and std::bind.

The below solution makes the following assumptions:

  • You know the functions you want to call and their arguments
  • The functions can have any signature, and any number of arguments
  • You want to use type erasure to be able to store these functions and arguments, and call them all at a later point in time

Here is a working example:

#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();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top