Повторное создание сигнатуры функции и вызов через пакеты шаблонов в C ++ - PullRequest
0 голосов
/ 14 января 2019

У меня есть код на C, который я хочу переписать на C ++. Код C является частью интерпретатора, где функции определены в C, однако фактический вызов происходит из интерпретируемого источника. В основном то, что он делает, перечислено ниже:

#include <vector>

void f1(int a0) { }
void f2(int a0,int a1) { }
void f3(int a0,int a1,int a2) { }
void f4(int a0,int a1,int a2,int a3) { }

struct m {
    void *p;
    int c;
};

std::vector<m> ma;

int addfunc(void *p, int c) {
    int i = ma.size();
    ma.push_back({p,c});
    return i;
}

void call(int idx, int *stack) {
    switch (ma[idx].c) {
    case 1:
    ((void (*)(int))ma[idx].p)  (stack[0]);
    break;
    case 2:
    ((void (*)(int,int))ma[idx].p)  (stack[0],stack[1]);
    break;
    case 3:
    ((void (*)(int,int,int))ma[idx].p)  (stack[0],stack[1],stack[2]);
    break;
    case 4:
    ((void (*)(int,int,int,int))ma[idx].p)  (stack[0],stack[1],stack[2],stack[3]);
    break;
    }
}

int main (void) {
    int stack[5] = { 0,1,2,3,4 };
    /* define */
    int i1 = addfunc((void*)f1, 1);
    int i2 = addfunc((void*)f2, 2);
    int i3 = addfunc((void*)f3, 3);
    int i4 = addfunc((void*)f4, 4);
    /* call */
    call(i1,stack);
    call(i2,stack);
    call(i3,stack);
    call(i4,stack);
}

addfunc создает вызываемый объект, указанный указателем функции и сигнатурой, поскольку аргументы имеют одинаковый тип, в то время как для количества аргументов необходим только аргумент count. Когда я call функция, я указываю индекс объекта функции и stack. Фактический c-call декодируется через счетчик аргументов и typecasted, аргументы вызова берутся из стека.

Как мне переписать функции addfunc и call как объекты шаблонов в C ++? Как я могу использовать пакеты шаблонов для подсчета количества аргументов для данной функции и повторного вызова функции? Как мне избавиться от оператора switch и указателя функции typecast ? Я видел, что класс luawrapper Binder делает нечто подобное. Однако код довольно сложный. В моем случае все аргументы одного типа. В конце я хочу сделать что-то вроде (псевдокод):

vector<meth> ma;
...
int i0 = addfunc([](int a) { });
int i1 = addfunc([](int a,int b) { });
int i2 = addfunc([](int a,int b,int b) { });
int i3 = addfunc([](int a,int b,int c,int c) { });
...
ma[i0](stack);
ma[i1](stack);
ma[i2](stack);
ma[i3](stack);

Ответы [ 5 ]

0 голосов
/ 15 января 2019

Вот код luawrapper , извлеченный для применения в вышеуказанном случае. Это больше для завершения, так как для @ Jerod42 код предпочтительнее .

#include <iostream>
#include <string>
#include <array>
#include <vector>
#include <functional>
#include <vector>

template<typename T> struct tag {};

template<typename TFunctionObject, typename TFirstParamType>
struct Binder {
    TFunctionObject function;
    TFirstParamType param;

    template<typename... TParams>
    auto operator()(TParams&&... params)
    -> decltype(function(param, std::forward<TParams>(params)...))
    {
        return function(param, std::forward<TParams>(params)...);
    }
};

template<typename TCallback>
static void readIntoFunction(int *stack, TCallback&& callback)
{
    callback();
}
template<typename TCallback, typename TFirstType, typename... TTypes>
static void readIntoFunction(int *stack, TCallback&& callback, tag<TFirstType>, tag<TTypes>... othersTags)
{
    Binder<TCallback, const TFirstType&> binder{ callback, *stack };
    return readIntoFunction(++stack, binder, othersTags...);
}

/* decompose arguments */
template<typename TFunctionType, typename... TOtherParams>
std::function<void(int*)> _addfunc(TFunctionType f, tag<void (*)(TOtherParams...)>) {
    return std::function<void(int*)>([f](int *stack) {
        readIntoFunction(stack, f, tag<TOtherParams>{}...);
    });
}

template<typename TFunctionType>
std::function<void(int*)> addfunc(TFunctionType fn)
{
    typedef typename std::decay<TFunctionType>::type RealFuncSig;
    return _addfunc(std::move(fn), tag<RealFuncSig>{} );
}

void f1(int a0) { std::cout << a0 << std::endl; }
void f2(int a0, int a1) { std::cout << a0 << a1 << std::endl;  }

int main() {
    int stack[5] = { 0,1,2,3,4 };
    auto a0 = addfunc(&f1);
    auto a1 = addfunc(&f2);

    a0(stack);
    a1(stack);
}
0 голосов
/ 15 января 2019

Я предлагаю решение C ++ 17 (упрощенное после наблюдения Jarod42: спасибо), которое, я полагаю, является слишком сложным.

Но я нахожу это забавным ...

Во-первых: структура, которая, учитывая (в качестве параметров шаблона) тип и число без знака, определяет type как полученный тип.

template <typename T, std::size_t>
struct getType
 { using type = T; };

Используется для преобразования списка чисел шаблона переменной в последовательность типов (int s, в следующем примере) одинаковой длины.

Далее: тип шаблона, который регистрирует (setFunc()) и exec (callFunc()) функцию, возвращающую void и последовательность длиной int s в качестве первого параметра шаблона.

template <std::size_t N, typename = std::make_index_sequence<N>>
struct frHelper;

template <std::size_t N, std::size_t ... Is>
struct frHelper<N, std::index_sequence<Is...>>
 {
   using fnPnt_t = void(*)(typename getType<int, Is>::type...);

   fnPnt_t fp = nullptr;

   void setFunc (fnPnt_t fp0)
    { fp = fp0; }

   void callFunc (std::array<int, sizeof...(Is)> const & a)
    { if ( fp ) fp(a[Is]...); }
 };

Последнее: шаблонная структура, которая наследуется из списка переменных предыдущих структур и включает (using) элементы setFunc() и callFunc().

template <std::size_t N, typename = std::make_index_sequence<N>>
struct funcRegister;

template <std::size_t N, std::size_t ... Is>
struct funcRegister<N, std::index_sequence<Is...>>
   : public frHelper<Is>...
 { 
   using frHelper<Is>::setFunc...;
   using frHelper<Is>::callFunc...;
 };

Использование.

Сначала вы должны объявить объект типа funcRegister<N>, где N - максимальное число целых чисел, полученных из ваших функций, плюс единица. Итак, если вы хотите использовать f4(), то есть четыре целых числа, вы должны объявить

   funcRegister<5u>  fr;

Тогда вам нужно зарегистрировать функции

   fr.setFunc(f1);
   fr.setFunc(f2);
   fr.setFunc(f3);
   fr.setFunc(f4);

и, учитывая std::array<int, N> нужного размера, вы можете вызвать зарегистрированные функции

   std::array a1 { 1 };
   std::array a2 { 1, 2 };
   std::array a3 { 1, 2, 3 };
   std::array a4 { 1, 2, 3, 4 };

   fr.callFunc(a1); // call f1
   fr.callFunc(a2); // call f2
   fr.callFunc(a3); // call f3
   fr.callFunc(a4); // call f4

Ниже приведен пример полной компиляции C ++ 17

#include <array>
#include <utility>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t>
struct getType
 { using type = T; };

template <std::size_t N, typename = std::make_index_sequence<N>>
struct frHelper;

template <std::size_t N, std::size_t ... Is>
struct frHelper<N, std::index_sequence<Is...>>
 {
   using fnPnt_t = void(*)(typename getType<int, Is>::type...);

   fnPnt_t fp = nullptr;

   void setFunc (fnPnt_t fp0)
    { fp = fp0; }

   void callFunc (std::array<int, sizeof...(Is)> const & a)
    { if ( fp ) fp(a[Is]...); }
 };

template <std::size_t N, typename = std::make_index_sequence<N>>
struct funcRegister;

template <std::size_t N, std::size_t ... Is>
struct funcRegister<N, std::index_sequence<Is...>>
   : public frHelper<Is>...
 { 
   using frHelper<Is>::setFunc...;
   using frHelper<Is>::callFunc...;
 };

void f1(int) { std::cout << "f1 called" << std::endl; }
void f2(int,int) { std::cout << "f2 called" << std::endl;}
void f3(int,int,int) { std::cout << "f3 called" << std::endl;}
void f4(int,int,int,int) { std::cout << "f4 called" << std::endl;}

int main()
 {
   funcRegister<5u> fr;

   fr.setFunc(f1);
   fr.setFunc(f2);
   fr.setFunc(f3);
   fr.setFunc(f4);

   std::array a1 { 1 };
   std::array a2 { 1, 2 };
   std::array a3 { 1, 2, 3 };
   std::array a4 { 1, 2, 3, 4 };

   fr.callFunc(a1);
   fr.callFunc(a2);
   fr.callFunc(a3);
   fr.callFunc(a4);    
 }
0 голосов
/ 14 января 2019

К сожалению, вы не сможете сделать полностью универсальное решение, поскольку нет способа стереть арность.

Один из способов упростить процесс - создать набор оберток для ваших функций, каждый из которых принимает stack*, и вызывать функции реализации с аргументами из указанного stack.

Если вам не нужны типы типов вообще, а просто указатель на функцию (для оценки оболочки) (даже не нужно стирать).

0 голосов
/ 14 января 2019

Ну, если они просто функции на C, почему бы не перегружать указатель на тип функции?

std::function<void(std::array<int, 5>)> addfunc(void (*f)(int)) {
    return [f](std::array<int, 5> const& a) { f(a[0]); };
}

std::function<void(std::array<int, 5>)> addfunc(void (*f)(int,int)) {
    return [f](std::array<int, 5> const& a) { f(a[0], a[1]); };
}

// repeat for all necessary arities

Затем создайте std::vector<std::function<void(std::array<int, 5>)>> и отодвиньте все свои функции. Это просто, не требует никаких шаблонов и будет работать достаточно хорошо. Это вводит издержки std::function, хотя.

Вы можете избавиться от этого, введя свой собственный вызываемый тип (n из них), который будет соответствовать приведенным выше перегрузкам, предоставит operator() и сохранит соответствующий тип функции внутри.

Живой пример .

0 голосов
/ 14 января 2019

вы можете использовать std: function в качестве параметра addfun (), а также std :: bind

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...