通过转换可变模板参数来组成可变模板参数

Compose variadic template argument by transforming them

本文关键字:参数 转换      更新时间:2023-10-16

我有一个简单的情况,可能需要复杂的方法来解决,但我不确定。

基本上,我有一个封装了成员函数的对象:

template<class T, typename R, typename... ARGS>
class MemberFunction
{
private:
  using function_type = R (T::*)(ARGS...);
  function_type function;
public:
  MemberFunction(function_type function) : function(function) { }
  void call(T* object, ARGS&&... args)
  {
    (object->*function)(args...);
  }   
};

这可以很容易地使用

MemberFunction<Foo, int, int, int> function(&Foo::add)
Foo foo;
int res = function.call(&foo, 10,20)

问题是,我想通过一个自定义环境来调用它,该环境使用一堆值来操作此方法,这转化为以下代码:

int arg2 = stack.pop().as<int>();
int arg1 = stack.pop().as<int>();
Foo* object = stack.pop().as<Foo*>();
int ret = function.call(object, arg1, arg2);
stack.push(Value(int));

这很容易直接在代码中完成,但我想找到一种方法,通过公开一个void call(Stack& stack)方法,将这种行为直接封装到MemberFunction类中,该方法为我获得以下内容:

MemberFunction<Foo, int, int, int> function(&Foo::add);
Stack stack;
stack.push(Value(new Foo());
stack.push(10);
stack.push(20);
function.call(stack);
assert(stack.pop().as<int>() == Foo{}.add(10,20));

但是,由于我是一个新的可变模板,我不知道我如何才能做到高效和优雅。

编辑:添加有关Stack和StackValue的详细信息

我所说的堆栈是std::stack<StackValue>的包装器,它提供了推送和弹出元素的模板方法,类似于

struct StackValue
{
  union
  {
    float fvalue;
    s32 ivalue;
    bool bvalue;
    FloatPair fpair;
    IntPair ipair;
    void* ptr;
  };
  template<typename T> T as();
  template<typename T> StackValue(T type);
  StackValue() { }
};
template<> inline StackValue::StackValue(float f) : fvalue(f) { }
template<> inline StackValue::StackValue(s32 i) : ivalue(i) { }
...
template<> inline float StackValue::as<float>() { return fvalue; }
template<> inline s32 StackValue::as<s32>() { return ivalue; }
...
class Stack
{
private:
  std::stack<StackValue> stack;
public:
  StackValue& peek() { return stack.top(); }
  StackValue pop() { StackValue v = stack.top(); stack.pop(); return v; }
  void push(StackValue value) { stack.push(value); }
  template<typename T> void pushValue(T value) { stack.push(StackValue(value)); }
  template<typename T> T popValue() {
      StackValue v = stack.top().as<T>();
      stack.pop();
      return v;
  }
}

为了保证以正确的顺序弹出内容,我们必须递归地执行此操作:

void call(Stack& s) {
    call_impl(std::integral_constant<int, sizeof...(ARGS)>{}, s);
}

带有:

template <int N, typename... StackVals>
void call_impl(std::integral_constant<int, N>, Stack& s, StackVals... vals) {
    call_impl(std::integral_constant<int, N-1>{}, s, s.pop(), vals...);
}
template <typename... StackVals
void call_impl(std::integral_constant<int, 0>,
               Stack& s,
               StackVals... vals)
{
    // now we have all the args
    T* object = s.pop().as<T*>();
    // so just call
    s.push(call(object, vals.as<Args>()...));
}

首先,我们把所有的论点一个接一个地pop,到目前为止,我们把下一个pop()-ed的论点放在其他论点之前。那么,关键表达式是:

vals.as<Args>()...

vals是我们刚刚建立的StackValues的参数包,Args是函数的参数包。如果我们做得对,这两个包的大小应该相同(否则,这将无法编译(。扩展将同时扩展为:

val0.as<Arg0>, val1.as<Arg1>, val2.as<Arg2>, ...

这正是我们想要的。我们只需要弹出T*,并将其作为call()的第一个参数。


旁注,此签名:

void call(T* object, ARGS&&... args);

不正确。这需要大量的右值引用。您要么想使用ARGS...,要么将其作为一个函数模板来进行转发引用。此外,它可能应该返回R