Łuk*_*Lew 7 c++ templates variadic-templates c++11 c++14
这是代码,我希望解释我想要实现的目标.
vector<int> ints;
vector<double> doubles;
struct Arg {
enum Type {
Int,
Double
};
Type type;
int index;
};
template <typename F>
void Call(const F& f, const vector<Arg>& args) {
// TODO:
// - First assert that count and types or arguments of <f> agree with <args>.
// - Call "f(args)"
}
// Example:
void copy(int a, double& b) {
b = a;
}
int test() {
Call(copy, {{Int, 3}, {Double, 2}}); // copy(ints[3], double[2]);
}
Run Code Online (Sandbox Code Playgroud)
这可以在C++ 11中完成吗?
如果是,可以在C++ 14中简化解决方案吗?
Yak*_*ont 10
我分两步完成.
首先,我将包装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; }
};
Run Code Online (Sandbox Code Playgroud)
我们映射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...);
};
}
Run Code Online (Sandbox Code Playgroud)
现在剩下的就是计算函数参数计数与向量大小计数,并将其解包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 );
}
Run Code Online (Sandbox Code Playgroud)
在哪里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 );
}
Run Code Online (Sandbox Code Playgroud)
处理投掷是一种练习,处理返回值也是如此.
此代码尚未编译或测试,但设计应该是合理的.它只支持调用函数指针 - 调用通用可调用对象很棘手,因为计算它们需要多少个参数(类型为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...>>{}>
{};
Run Code Online (Sandbox Code Playgroud)
是一个类型的包.
以下是我们如何存储缓冲区:
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;
}
};
Run Code Online (Sandbox Code Playgroud)
现在我们修改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...>{} );
}
Run Code Online (Sandbox Code Playgroud)
这是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() } );
Run Code Online (Sandbox Code Playgroud)
参数不匹配计数和索引超出范围会产生抛出的错误.它只适用于原始函数指针 - 使其适用于具有固定(非模板)签名的任何内容都很容易(如a std::function
).
使其与没有签名的对象一起工作更难.基本上,您不是依赖于为参数调用的函数,而是构建types<Ts...>
最多为某个固定大小的叉积.您构建一个(大)表,其中哪些是对传入的调用目标的有效调用(在编译时),然后在运行时遍历该表并确定传入的参数是否有效以调用该对象.
它变得凌乱.
这就是为什么我的上面的版本只是要求索引,并从被调用的对象中推断出类型.