28

所以我有一些类型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 )

4

5 回答 5

30

您可以重构或包装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));
}

活生生的例子。

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

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

活生生的例子。或者(也许更好),您也可以只提供一个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)...});
}

活生生的例子。
编辑:另一个选择是使用临时数组:

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));
}

活生生的例子。请注意,该as_lvalue函数是危险的,数组仍然只存在到完整表达式的末尾(在这种情况下g),所以使用它时要小心。Alias是必需的,因为X[]{ ... }由于语言语法,just 是不允许的。

如果所有这些都不可能,您将需要递归来访问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);
}

活生生的例子。

编辑:受 ecatmur 评论的启发,我使用了索引技巧使其仅与包扩展和原样工作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);
}

活生生的例子。

于 2012-08-19T23:03:46.590 回答
7

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

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) {
    [](...){}((f(std::forward<Args>(args)), 0)...);
}
于 2015-02-02T14:04:06.447 回答
6

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

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来定义数组的大小:没有任何东西向编译器表明它应该是一个常量表达式。

于 2012-08-19T22:49:50.060 回答
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
    pass{unused};

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

应该可以写

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

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

活生生的例子


作为替代方案,这是 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:我可以从多个 args 转到 tuple,但我可以从 tuple 转到多个 args 吗?了解更多信息。 活生生的例子

于 2012-08-19T23:46:09.873 回答
0

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

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

于 2013-05-01T15:58:47.067 回答