c++ - C++11和C++14如何实现动态函数调用?

标签 c++ templates c++11 c++14 variadic-templates

这是我希望能解释我想要实现的目标的代码。

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_sequenceindex_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/

相关文章:

c++ - const 方法的 const 引用返回的线程安全

c++ - 递归取消引用指针

c++ - gcc 4.7 有时无法使用指针模板参数

c++ - 如何将 XCode 的 libc++ 与 top-of-trunk clang 一起使用?

c++ - 如何删除在另一个函数中初始化的动态分配的数组?

c++ - decltype(std) 是否合法,是否有任何用途?

c++ - 模板模板参数或模板参数

c++ - 编译时浮点初始化的替代方法

c++ - C++0x 的哪些功能肯定会保留(如果有的话)?

C++:提高现有 C 程序的性能