为每个可变参数模板参数和一个数组调用一个函数

Posted

技术标签:

【中文标题】为每个可变参数模板参数和一个数组调用一个函数【英文标题】:Calling a function for each variadic template argument and an array 【发布时间】:2012-08-15 08:42:32 【问题描述】:

所以我有一些类型X

typedef ... X;

还有一个模板函数f:

class <typename T>
void f(X& x_out, const T& arg_in);

然后是一个函数g:

void g(const X* x_array, size_t x_array_size);

我需要编写一个可变参数模板函数h 来执行此操作:

template<typename... Args>
void h(Args... args)

    constexpr size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    for (int i = 0; i < nargs; i++) // foreach arg
        f(x_array[i], args[i]); // call f (doesn't work)

    g(x_array, nargs); // call g with x_array

它不起作用的原因是因为你不能在运行时像这样下标 args。

替换h中间部分的最佳技术是什么?

获胜者是 Xeo:

template<class T> X fv(const T& t)  X x; f(x,t); return x; 

template<class... Args>
void h(Args... args)

  X x_array[] =  fv(args)... ;

  g(x_array, sizeof...(Args));

(实际上在我的具体情况下,我可以重写 f 以按值返回 x 而不是作为输出参数,所以我什至不需要上面的 fv)

【问题讨论】:

args 不是一个数组——你期待什么? @KerrekSB:当然。我没想到它会起作用,只是用它作为伪代码来描述问题。 由于您使用的是 C++11,您不能让 h 获取一个初始化列表并使用向量而不是数组吗? @jrok:h 的参数类型是异构的。我认为 initializer_list 是针对同质参数的。 【参考方案1】:

您可以重构或包装 f 以返回一个新的 X 而不是让它通过,因为这会发挥包扩展的作用并使函数真正简洁:

template<class T>
X fw(T const& t) X x; f(x, t); return x; 

template<class... Args>
void h(Args... args)
  X xs[] =  fw(args)... ;
  g(xs, sizeof...(Args));

Live example.

如果您可以将g 更改为只接受std::initializer_list,它会变得更加简洁:

template<class... Args>
void h(Args... args)
  g(f(args)...);

Live example. 或者(也许更好),你也可以只提供一个包装器g 转发到真正的g

void g(X const*, unsigned)

void g(std::initializer_list<X> const& xs) g(xs.begin(), xs.size()); 

template<class... Args>
void h(Args... args)
  g(f(args)...);

Live example.编辑:另一种选择是使用临时数组:

template<class T>
using Alias = T;

template<class T>
T& as_lvalue(T&& v) return v; 

template<class... Args>
void h(Args... args)
  g(as_lvalue(Alias<X[]>f(args)...), sizeof...(Args));

Live example. 注意as_lvalue 函数很危险,数组仍然只存在到完整表达式的末尾(在本例中为g),所以在使用时要小心。 Alias 是必需的,因为由于语言语法,只允许 X[] ...

如果所有这些都不可能,您将需要递归来访问 args 包的所有元素。

#include <tuple>

template<unsigned> struct uint_; // compile-time integer for "iteration"

template<unsigned N, class Tuple>
void h_helper(X (&)[N], Tuple const&, uint_<N>)

template<unsigned N, class Tuple, unsigned I = 0>
void h_helper(X (&xs)[N], Tuple const& args, uint_<I> = )
  f(xs[I], std::get<I>(args));
  h_helper(xs, args, uint_<I+1>());


template<typename... Args>
void h(Args... args)

    static constexpr unsigned nargs = sizeof...(Args);
    X xs[nargs];

    h_helper(xs, std::tie(args...));

    g(xs, nargs);

Live example.

编辑: 受 ecatmur 评论的启发,我使用了 indices trick 使其与包扩展以及 fg 原样工作,而不改变它们。

template<unsigned... Indices>
struct indices
  using next = indices<Indices..., sizeof...(Indices)>;
;
template<unsigned N>
struct build_indices
  using type = typename build_indices<N-1>::type::next;
;
template <>
struct build_indices<0>
  using type = indices<>;
;
template<unsigned N>
using IndicesFor = typename build_indices<N>::type;

template<unsigned N, unsigned... Is, class... Args>
void f_them_all(X (&xs)[N], indices<Is...>, Args... args)
  int unused[] = (f(xs[Is], args), 1)...;
  (void)unused;


template<class... Args>
void h(Args... args)
  static constexpr unsigned nargs = sizeof...(Args);
  X xs[nargs];
  f_them_all(xs, IndicesFor<nargs>(), args...);
  g(xs, nargs);

Live example.

【讨论】:

X xs[] = f(args)... ; 很酷。实际上我的问题的具体情况是 mysql 的 MYSQL_BIND (= X) 结构,我不确定它是否可以复制。但如果可以,那就太好了。 为所有代码 sn-ps 添加了实时示例,修复了几个拼写错误并为 g 提供了一个包装器选项。希望它对您的 MYSQL 内容有所帮助。 :) 实际上,如果您在此处查看代码示例:dev.mysql.com/doc/refman/5.5/en/mysql-stmt-execute.html。可以复制 MYSQL_BIND 结构,所以我认为您的解决方案会起作用。 在您的第一个示例中,我认为您可以丢弃 length_of 并再次调用 sizeof...,不是吗? @Andrew:是的,这是重构的剩余部分。起初,我只有X xs[],但我需要将其更改为X xs[sizeof...(Args)],因为无论出于何种原因,前者都不匹配T(&amp;)[N]。但是,显式添加数组大小有点不需要length_of 本身,所以是的......:P【参考方案2】:

很好的模板作为问题第一部分的答案:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) 
    [](...)((f(std::forward<Args>(args)), 0)...);

【讨论】:

模拟折叠表达式的非常有趣的方法。 这个解决方案虽然很漂亮,但不能保证评估顺序。见:godbolt.org/g/2KiZCD【参考方案3】:

很明显:您使用的不是迭代而是递归。在处理可变参数模板时,总是会出现递归。即使使用tie() 将元素绑定到std::tuple&lt;...&gt;,它也是递归的:恰好递归业务是由元组完成的。在您的情况下,您似乎想要这样的东西(可能有一些拼写错误,但总的来说应该可以):

template <int Index, int Size>
void h_aux(X (&)[Size]) 


template <int Index, int Size, typename Arg, typename... Args>
void h_aux(X (&xs)[Size], Arg arg, Args... args) 
    f(xs[Index], arg);
    h_aux<Index + 1, Size>(xs, args...);


template <typename... Args>
void h(Args... args)

    X xs[sizeof...(args)];
    h_aux<0, sizeof...(args)>(xs, args...);
    g(xs, sizeof...(args));

我认为你也不能使用nargs 来定义数组的大小:没有任何东西向编译器表明它应该是一个常量表达式。

【讨论】:

我有 gcc 扩展,所以我认为它可以处理动态大小的数组。但如果你不这样做,你可以像你所做的那样将 sizeof... 直接放入数组大小中。 @Andrew:你不应该依赖编译器扩展。 @Xeo:这取决于您项目的可移植性要求。许多扩展非常有用,因此如果您只针对单个平台,使用它们会更有意义。 @Andrew:即使那样你也不应该,需求会发生变化,对扩展的支持也会发生变化。 我认为这个论坛是关于 C++ 的,而不是关于带有扩展的 C++。有用的代码必然会被移植到不同的环境中,并且扩展的使用可能会成为阻碍。由于在这种情况下实际上并不需要支持可变大小的数组,因此我认为最好避免使用它。我发布的代码示例通过始终使用sizeof...() 来避免该问题,但使用constexpr 是另一种选择。在任何情况下,使用nargs 都不能用作模板参数,除非它是一个常量表达式。【参考方案4】:

用参数包扩展做起来相当简单,即使你不能重写f来按值返回输出参数:

struct pass  template<typename ...T> pass(T...)  ;

template<typename... Args>
void h(Args... args)

    const size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    X *x = x_array;
    int unused[](f(*x++, args), 1)...; // call f
    passunused;

    g(x_array, nargs); // call g with x_array

应该可以写

    pass(f(*x++, args), 1)...; // call f

但似乎 g++(至少 4.7.1)有一个错误,它无法将大括号初始化器列表参数的评估排序为类初始化器。数组初始化器虽然没问题;有关更多信息和示例,请参阅 Sequencing among a variadic expansion。

Live example.


作为替代方案,这是 Xeo 提到的使用生成的索引包的技术;不幸的是,它确实需要一个额外的函数调用和参数,但它相当优雅(特别是如果你碰巧有一个索引包生成器):

template<int... I> struct index 
    template<int n> using append = index<I..., n>; ;
template<int N> struct make_index  typedef typename
    make_index<N - 1>::type::template append<N - 1> type; ;
template<> struct make_index<0>  typedef index<> type; ;
template<int N> using indexer = typename make_index<N>::type;

template<typename... Args, int... i>
void h2(index<i...>, Args... args)

    const size_t nargs = sizeof...(args); // get number of args
    X x_array[nargs]; // create X array of that size

    pass(f(x_array[i], args), 1)...; // call f

    g(x_array, nargs); // call g with x_array


template<typename... Args>
void h(Args... args)

  h2(indexer<sizeof...(args)>(), std::forward<Args>(args)...);

请参阅C++11: I can go from multiple args to tuple, but can I go from tuple to multiple args? 了解更多信息。 Live example.

【讨论】:

参数包是否保证按顺序求值?我似乎记得不是,但我可能记错了。 @AndrewTomazos-Fathomling 参数包没有任何特殊的评估顺序;在这里,我使用了一个括号括起来的列表,它保证从左到右 (8.5.4:4) 进行评估。 如果您不使 h 接受通用引用 (Args&amp;&amp;...),则不需要 std::forward【参考方案5】:

Xeo 的想法是正确的——您想要构建某种“可变参数迭代器”,以便在其余代码中隐藏很多这种讨厌的东西。

我会获取索引内容并将其隐藏在仿照 std::vector 的迭代器接口后面,因为 std::tuple 也是数据的线性容器。然后,您可以重用所有可变参数函数和类,而无需在其他任何地方使用显式递归代码。

【讨论】:

以上是关于为每个可变参数模板参数和一个数组调用一个函数的主要内容,如果未能解决你的问题,请参考以下文章

C语言中如何实现可变参函数

关于Java可变参数问题?

函数基础之调用,参数,可变长参数

可变参函数

如何使用可变参数模板参数保存可变数量的参数?

每个可变参数模板参数生成一个类成员