Как восстановить тип указателя функции во время выполнения

В коде я регистрирую один или несколько указателей на функции в классе менеджера.

В этом классе у меня есть карта, которая отображает типы аргументов функции на указанную функцию. Это может выглядеть так: 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 но это будет шаблонный тип, поэтому он не может быть сохранен в карте для функций с разными аргументами.


(Если неясно, что я спрашиваю, подумайте о LuaBinds возможность зарегистрировать перегруженные функции. Я пытался понять, как это реализовано там (без шаблонов с переменными параметрами, до C ++ 11), но безрезультатно.)

4

Решение

Предположим, у вас были аргументы в 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 это еще одна проблема. Это, вероятно, будет связано с трюком с индексами.

Предположим, мы поддерживаем два вида аргументов — double а также int, Аргументы во время выполнения затем загружаются в 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;

С этим мы можем написать 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 );
}

И это должно работать, когда funcАргументы точно совпадают с теми, что были переданы внутрь run_time_args,

Обратите внимание, что я был быстрым и свободным с неспособностью std::move тот std::vector вокруг. И что вышеупомянутое не поддерживает неявное преобразование типов. И я не скомпилировал ни один из вышеприведенного кода, поэтому он, вероятно, изобилует опечатками.

3

Другие решения

Несколько недель назад я возился с вариадическими шаблонами и придумал решение, которое могло бы вам помочь.

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

Не уверен, что ваши требования позволят это, но вы могли бы просто использовать std::function а также std::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();
}
0
По вопросам рекламы [email protected]