C++ 可变参数模板和求值顺序

3
我有以下代码:

lib.hxx:

template <typename C, typename R, typename ... Args>
R Lib::extract_call(lua_State* L, R(C::*method)(Args...))
{
  return static_cast<C*>(this)->*method(extract_data<Args>(L)...);
}

lib.cc:

template <>
std::string Lib::extract_data(lua_State* L)
{
  if (! lua_isstring(L, -1))
  {
    return "";
  }

  return lua_tostring(L, -1);
}
[...] // Other specializations following

我正在将lua嵌入到一个项目中,目前正在寻找一种从lua中调用方法的方法,并从调度程序自动从lua堆栈中提取参数。通过这些“简单”的模板,可以轻松生成所需的调用,以从给定参数中提取数据,而不会出现任何打字错误。
但是,我的问题是,当解包extract_data<Args>(L)...时,所有extract_data调用的评估顺序未指定(如标准所述,为了优化目的),而从lua堆栈中提取数据的顺序确实很重要。 另一方面,我无法将所有这些调用分组在初始化器列表中,因为它们具有不同的类型。
因此,如何确保extract_data调用按特定顺序进行,或者至少保持自动传递参数到我的成员指针函数的方式?
编辑:我忘记了调用需要按反转顺序进行,我认为任何语言机制都无法实现这一点。 因此,这是我的解决方案,回到常规的非可变模板:
template <typename C, typename R, typename A1>
R Lib::extract_call(lua_State* L, R(C::*method)(A1))
{
  return (static_cast<C*>(this)->*method)(extract_data<A1>(L));
}

template <typename C, typename R, typename A1, typename A2>
R Lib::extract_call(lua_State* L, R(C::*method)(A1, A2))
{
  A2 b = extract_data<A2>(L);
  A1 a = extract_data<A1>(L);

  return (static_cast<C*>(this))->*method(a,b);
}

template <typename C, typename R,
          typename A1, typename A2, typename A3>
R Lib::extract_call(lua_State* L, R(C::*method)(A1, A2, A3))
{
  A3 c = extract_data<A3>(L);
  A2 b = extract_data<A2>(L);
  A1 a = extract_data<A1>(L);

  return (static_cast<C*>(this))->*method(a,b,c);
}
// And so on up to 8 arguments

反向求值实际上相当容易 - 只需在提取参数之前调用递归情况即可。复杂性来自于需要收集返回值。不过,你可以轻松地通过 std::tuple 来完成这个任务:http://coliru.stacked-crooked.com/a/7935d16232f21a5c。最终的解包(unpacking)可以使用 index-sequence 来完成。 - Xeo
2个回答

2

如果你能够修改方法,只需要传入单个std::tuple<Args...>而不是多个Args...,那么你就可以将extract_data放在一个花括号初始化器中:

return static_cast<C*>(this)->*method({extract_data<Args>(L)...});

与函数参数不同,初始值设定项的子句从左到右进行顺序求值。


问题是- 他能做到吗?也许所调用的函数是一些他无法控制的 API 的一部分? - j_kubik
@j_kubik:确实。如果你不能改变它,那么你就无法做到这一点。 - Mike Seymour

1

现在我们将使用更复杂的模板。下面的代码是我没有编译器写的,可能会有一些错误:

template <unsigned int n>
class tuple_extractor{
    template <typename T, typename ...ArgsOut, typename ...ArgsIn, typename ...ArgsPart>
    static void extractTuple(
            T* obj,
            void (T::*func)(ArgsOut...),
            const std::tuple<ArgsIn...>& inParams,
            ArgsPart... partParams){
        tuple_extractor<n-1>::extractTuple(obj, func, inParams, std::get<n-1>(inParams));
    }
};

template <>
class tuple_extractor<0>{
    template <typename T, typename ...ArgsOut, typename ...ArgsIn>
    static void extractTuple(
            T* obj,
            void (T::*func)(ArgsOut...),
            const std::tuple<ArgsIn...>& inParams,
            ArgsIn... partParams){
        obj->func(partParams...);
    }
};

template <typename C, typename R, typename ... Args>
R Lib::extract_call(lua_State* L, R(C::*method)(Args...))
{
    std::tuple<Args...> tmp{extract_data<Args>(L)...};
    tuple_extractor<sizeof...(Args)>::extractTuple(static_cast<C*>(this), method, tmp);
}

看起来GCC存在一个错误,会影响大括号初始化的顺序。如果你使用受影响的版本,只需使用

// For first-to-last order use:
template <typename T, typename ...Args>
inline std::tuple<T, Args...> getTuple(lua_State* L){
    return std::tuple_cat(std::make_tuple<T>(extract_data<T>(L)), getTuple<Args...>(L));
}

template <typename T>
inline std::tuple<T> getTuple(lua_State* L){
    return std::make_tuple<T>(extract_data<T>(L));
}

    template <typename C, typename R, typename ... Args>
R Lib::extract_call(lua_State* L, R(C::*method)(Args...))
{
    std::tuple<Args...> tmp = getTuple<Args...>(L);
    tuple_extractor<sizeof...(Args)>::extractTuple(static_cast<C*>(this), method, tmp);
}

首先,我们按顺序创建一个包含所有参数的元组,然后将获得的元组提取为方法调用的参数。

这是对所发布问题的答案。但是我同意@Mike的观点 - 如果您可以更改通过指针调用的方法的原型,则应该为元组添加一个重载并将其作为一个参数传递。上面的代码原则上可以几乎完全内联,并且几乎不会造成性能开销,但我不确定今天的编译器实际上会怎么做。

编辑:

可编译版本在此处:


我没有测试过你的代码,但这正是我在思考递归模板时想要达到的目标,但并未实现可用的东西。然而,在我的情况下仍存在一个问题:需要从 Lua 栈中以相反的顺序提取参数,而我不认为这是可能的... - Sylomex
我无法编译您的代码,即使进行了一些修复,模板错误变得如此棘手,以至于我放弃了。我改用非可变参数模板来解决问题。 - Sylomex
如果我有时间,我可能会尝试使它可编译,并且我相当确定也可以使用相反的顺序。 - j_kubik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接