这是我希望能解释我想要实现的目标的代码。
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]);
}
这可以在 C++11 中完成吗?
如果是,能否在 C++14 中简化解决方案?
最佳答案
我会分两步完成。
首先,我将包装 f
在一个能够理解的对象中 Arg
-like 参数,并在失败时生成错误。为简单起见,假设我们抛出。
这比你的 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; }
};
我们 map 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...);
};
}
现在剩下的就是计算函数参数数与 vector 大小数,并解压 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 );
}
处理抛出问题留作练习,处理返回值也是如此。
此代码未经编译或测试,但设计应该是合理的。它只支持调用函数指针——调用广义的可调用对象很棘手,因为计算它们需要多少参数(int 或 double 类型)很棘手。如果您将他们想要的参数数量作为编译时常量传入,这很容易。您还可以构建一个魔法开关来处理某个常量(10、20、1000 等)的计数,并将 vector 的运行时长度分派(dispatch)到一个编译时常量中,该常量会引发参数长度不匹配。
这比较棘手。
硬编码指针有点糟糕。
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...>
的叉积。达到一些固定大小。您构建一个(大)表,其中哪些是对传入调用目标的有效调用(在编译时),然后在运行时遍历该表并确定传入的参数是否有效以调用对象。
它变得凌乱。
这就是为什么我的上述版本只要求索引,并从被调用的对象中推导出类型。
关于c++ - C++11和C++14如何实现动态函数调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31932852/