我会分两步完成。
首先,我将f 包装在一个能够理解Arg 类参数的对象中,并在失败时生成错误。为简单起见,假设我们抛出。
这比你的Arg在这一层理解要简单一些,所以我可能会将Arg翻译成MyArg:
struct MyArg {
MyArg(MyArg const&)=default;
MyArg(int* p):i(p){}
MyArg(double* p):d(p){}
MyArg(Arg a):MyArg(
(a.type==Arg::Int)?
MyArg(&ints.at(a.index)):
MyArg(&doubles.at(a.index))
) {}
int * i = nullptr;
double* d = nullptr;
operator int&(){ if (!i) throw std::invalid_argument(""); return *i; }
operator double&(){ if (!d) throw std::invalid_argument(""); return *d; }
};
我们将void(*)(Ts...) 映射到std::function<void(MyArg, MyArg, MyArg)>,如下所示:
template<class T0, class T1>using second_type = T1;
template<class...Ts>
std::function<void( second_type<Ts,MyArg>... )> // auto in C++14
my_wrap( void(*f)(Ts...) ) {
return [f](second_type<Ts,MyArg>...args){
f(args...);
};
}
现在剩下的就是计算函数参数计数与向量大小计数,并将std::vector 解压缩到函数调用中。
最后的样子:
template<class...Ts, size_t...Is>
void call( std::function<void(Ts...)> f, std::index_sequence<Is...>, std::vector<Arg> const& v ) {
f( v[Is]... );
}
template<class...Ts>
void call( std::function<void(Ts...)> f, std::vector<Arg> const& v ) {
call( std::move(f), std::index_sequence_for<Ts...>{}, v );
}
其中index_sequence 和index_sequence_for 是C++14,但等价物可以在C++11 中实现(栈溢出有很多实现)。
所以我们最终会得到这样的结果:
template<class...Ts>
void Call( void(*pf)(Ts...), std::vector<Arg> const& v ) {
if (sizeof...(Ts)>v.size())
throw std::invalid_argument("");
auto f = my_wrap(pf);
call( std::move(f), v );
}
处理 throws 作为练习,处理返回值也是如此。
此代码尚未编译或测试,但设计应该是合理的。它只支持调用函数指针——调用通用的可调用对象很棘手,因为计算它们想要多少个参数(类型为 int 或 double)很棘手。如果你传入了他们想要多少个参数作为编译时常量,这很容易。您还可以构建一个魔术开关来处理最多为某个常数(10、20、1000 等)的计数,并将向量的运行时长度分派到一个编译时间常数中,该常数会引发参数长度不匹配。
这比较棘手。
硬编码指针有点糟糕。
template<class...Ts>struct types{using type=types;};
template<size_t I> using index=std::integral_constant<size_t, I>;
template<class T, class types> struct index_in;
template<class T, class...Ts>
struct index_in<T, types<T,Ts...>>:
index<0>
{};
template<class T, class T0, class...Ts>
struct index_in<T, types<T0,Ts...>>:
index<1+index_in<T, types<Ts...>>{}>
{};
是一个类型的包。
这是我们如何存储缓冲区:
template<class types>
struct buffers;
template<class...Ts>
struct buffers<types<Ts...>> {
struct raw_view {
void* start = 0;
size_t length = 0;
};
template<class T>
struct view {
T* start = 0;
T* finish = 0;
view(T* s, T* f):start(s), finish(f) {}
size_t size() const { return finish-start; }
T& operator[](size_t i)const{
if (i > size()) throw std::invalid_argument("");
return start[i];
}
}
std::array< raw_view, sizeof...(Ts) > views;
template<size_t I>
using T = std::tuple_element_t< std::tuple<Ts...>, I >;
template<class T>
using I = index_of<T, types<Ts...> >;
template<size_t I>
view<T<I>> get_view() const {
raw_view raw = views[I];
if (raw.length==0) { return {0,0}; }
return { static_cast<T<I>*>(raw.start), raw.length/sizeof(T) };
}
template<class T>
view<T> get_view() const {
return get_view< I<T>{} >();
}
template<class T>
void set_view( view<T> v ) {
raw_view raw{ v.start, v.finish-v.start };
buffers[ I<T>{} ] = raw;
}
};
现在我们修改Call:
template<class R, class...Args, size_t...Is, class types>
R internal_call( R(*f)(Args...), std::vector<size_t> const& indexes, buffers<types> const& views, std::index_sequence<Is...> ) {
if (sizeof...(Args) != indexes.size()) throw std::invalid_argument("");
return f( views.get_view<Args>()[indexes[Is]]... );
}
template<class R, class...Args, size_t...Is, class types>
R Call( R(*f)(Args...), std::vector<size_t> const& indexes, buffers<types> const& views ) {
return internal_call( f, indexes, views, std::index_sequence_for<Args...>{} );
}
这是C++14,但大多数组件都可以翻译成C++11。
这使用 O(1) 数组查找,没有映射。你负责用缓冲区填充buffers<types>,有点像这样:
buffers<types<double, int>> bufs;
std::vector<double> d = {1.0, 3.14};
std::vector<int> i = {1,2,3};
bufs.set_view<int>( { i.data(), i.data()+i.size() } );
bufs.set_view<double>( { d.data(), d.data()+d.size() } );
参数不匹配计数和超出范围的索引会产生抛出错误。它只适用于原始函数指针——使它适用于任何具有固定(非模板)签名的东西很容易(比如std::function)。
让它与没有签名的对象一起工作更难。基本上,不是依赖为参数调用的函数,而是将types<Ts...> 的叉积构建到某个固定大小。您构建一个(大)表,其中哪些是对传入调用目标的有效调用(在编译时),然后在运行时遍历该表并确定传入的参数是否有效以调用对象。
它变得一团糟。
这就是为什么我上面的版本只是要求索引,并从被调用的对象中推断出类型。