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