Оценить переменные параметры из виртуального стека

Я делаю небольшой движок скриптов на основе стека для изучения встроенных скриптов в 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.

Другие вопросы по тегам