Оценить переменные параметры из виртуального стека
Я делаю небольшой движок скриптов на основе стека для изучения встроенных скриптов в C++. Цель состоит в том, чтобы иметь возможность зарегистрировать любой std::function
быть вызванным скриптом. То, что я имею сейчас, по сути
class Bytecode
{
private:
Stack stack;
// Functions to be called from script.
// When a function is called, its arguments are expected to be in the stack.
std::vector<std::function<void(void)> > ops;
public:
// Register C++ function to be called by script
template<typename Func, typename T, typename... Args>
std::size_t function(Func fn, T arg, Args... args)
{
// Substitute value from the stack to function parameter.
auto fn2 = [fn,this](Args ...args) { fn(stack.pop().number, args...); };
return function(fn2, args...);
}
template<typename Func, typename T>
std::size_t function(Func fn, T arg)
{
std::function<void(void)> fn2 = [fn, this]() { fn(stack.pop().number); };
return function(fn2);
}
template<typename Func>
std::size_t function(Func fn)
{
ops.push_back(fn);
// Return bytecode of the function (the same as ops index).
return ops.size() - 1;
}
};
Тогда я могу сделать
void myfunc(double a, double b)
{
std::cout << a + b << std::endl;
}
int main()
{
Bytecode bytecode;
// The last two arguments are dummy
auto op = bytecode.function(myfunc, 3.4, 3.6);
}
Так что это работает, но я бы не стал приводить фиктивные аргументы. Я пробовал перегрузить std::size_t function(std::function<void(T, Args...>) fn)
но безуспешно, потому что кажется, что специализация std::function
Аргументы шаблона не работают так же, как обычные аргументы шаблона. Есть идеи?
Решение
В конце концов мне удалось получить какое-то рабочее решение частично методом проб и ошибок. Извините, что вопрос был несколько расплывчат в отношении обязательных вариантов использования.
template<class T, class... Args>
std::size_t function(std::function<void(T,Args...)> &&fn)
{
// Substitute value from the stack to function parameter.
auto fn2 = [fn, this](Args ...args) { fn(stack.pop().as<T>(), std::forward<Args>(args)...); };
return function(std::forward<std::function<void(Args...)> >(fn2));
}
std::size_t function(std::function<void(void)> &&fn)
{
ops.push_back(fn);
// Return bytecode of the function (the same as ops index).
return ops.size() - 1;
}
И вариант использования с функцией, которая имеет разные типы аргументов
void myfunc(double foo, int bar)
{
std::cout << foo + bar << std::endl;
}
int main()
{
Bytecode bytecode;
auto op = bytecode.function(std::function<void(double,int)>(myfunc));
}
Таким образом, указатель на функцию должен быть заключен в std::function
но я думаю это нормально.
1 ответ
Указатель на функцию не является std::function
, Вы не можете (начиная с C++14) определить тип std::function
шаблон из указателя функции. C++17 представляет такую функцию.
Один из подходов заключается в следующем:
template<typename Func, std::size_t...Is>
std::size_t function(Func fn, std::index_sequence<Is...>) {
std::function<void()> fn2 = [fn, this]() {
// array guarantees left-to-right evaluation:
double elems[] = { ( void(Is), stack.pop().number)... } // comma operator and init list
fn( elems[Is]... );
};
ops.push_back(fn2);
return ops.size()-1;
}
template<std::size_t N, typename Func>
std::size_t function(Func fn) {
return function(fn, std::make_index_sequence<N>{});
}
теперь вы просто указываете количество аргументов в точке вызова:
auto op = bytecode.function<2>(myfunc);
Теперь из указателей на функции вы можете вывести типы, но в более общих случаях вы не можете. И, в любом случае, быть ясным в отношении того, какие типы вы ожидаете, на мой взгляд, хорошая идея.
Это решение использует индексную последовательность C++ 14 и создает индексную последовательность. Короткие реализации C++ 11 каждой из них можно найти по всему переполнению стека.
namespace notstd {
template<std::size_T...Is>
struct index_sequence {};
template<std::size_t N, std::size_t...Is>
struct make_index_sequence:
make_index_sequence<N-1, N-1, Is...>
{};
template<std::size_t...Is>
struct make_index_sequence<0, Is...>:
index_sequence<Is...>
{};
}
Вышеприведенная реализация является относительно невысокой по качеству, но достаточно хороша для приведенного выше кода на C++ 11.