在C++中通过模板包重新创建函数签名和调用

Recreate function signature and call via template packs in C++

本文关键字:函数 创建 调用 包重新 C++      更新时间:2023-10-16

我有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调用通过参数计数进行解码并进行类型转换,调用参数取自堆栈。

如何在C++中将addfunccall函数重写为模板对象?如何使用模板包计算给定函数的参数数量并重新生成对该函数的调用?如何摆脱switch语句和函数指针类型转换?我已经看到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);

如果它们只是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()并在其中存储适当的函数类型。

活生生的例子。

不幸的是,您将无法制作一个完全通用的解决方案,因为无法键入擦除arity。

简化事情的一种方法是为函数创建一组包装器,每个包装器接受一个stack*,并使用所述stack中的参数调用实现函数。

您根本不需要类型转换,只需要一个简单的函数指针(指向适当的包装器)就可以了(甚至不需要键入擦除)。

我提出了一个C++17解决方案(根据Jarod42的观察结果简化:谢谢),我认为它过于复杂。

但我觉得很有趣。。。

首先:一个结构,给定(作为模板参数)一个类型和一个无符号数字,将type定义为接收的类型。

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

它用于转换具有相同长度的类型序列(在下面的示例中为ints)中的数字的可变模板列表。

下一步:一个模板类型,注册(setFunc())并执行(callFunc())一个返回void的函数和一个长度为int的序列作为第一个模板参数。

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是从函数接收的整数的最大值加1。所以如果你想使用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);    
}

以下是提取的luarwrapper代码,用于应用上述情况。这更多的是为了完成,因为@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);
}

您可以使用std:函数作为addfun()的参数,也可以使用std::bind