C++可变参数模板和评估顺序

C++ variadic templates and evaluation order

本文关键字:评估 顺序 变参 参数 C++      更新时间:2023-10-16

我有以下代码:

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堆栈中提取参数。从这些"简单"的模板中,可以轻松生成从参数中给出的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<Args...>,而不是多个Args...,则可以将extract_data放在大括号初始化器中:

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

与函数参数不同,初始化器子句的计算是从左到右排序的。

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

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 - 如果您可以更改指针调用的方法的原型,则应为元组添加重载并将其作为一个参数传递。上面的代码原则上几乎可以完全内联,并且导致很少的性能开销,但我不确定今天的编译器实际上会用它做什么。

编辑:

可编译版本在这里: