简体   繁体   English

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

[英]Calling a function for each variadic template argument and an array

So I have some type X : 所以我有一些X型:

typedef ... X;

and a template function f : 和模板函数f

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

and then a function g : 然后是一个函数g

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

I need to write a variadic template function h that does this: 我需要编写一个variadic模板函数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
}

The reason it doesn't work is because you can't subscript args like that at runtime. 它不起作用的原因是因为你不能在运行时下标这样的args。

What is the best technique to replace the middle part of h ? 替换h的中间部分的最佳技术是什么?

And the winner is Xeo: 获胜者是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));
}

(Actually in my specific case I can rewrite f to return x by value rather than as an out parameter, so I don't even need fv above) (实际上在我的具体情况下,我可以重写f以按值返回x而不是作为out参数,所以我甚至不需要上面的fv)

You could refactor or wrap f to return a new X instead of having it passed, since this would play pack expansion into the hand and make the function really concise: 你可以重构或包装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. 实例。

And if you could change g to just accept an std::initializer_list , it would get even more concise: 如果您可以将g更改为只接受std::initializer_list ,则会更简洁:

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

Live example. 实例。 Or (maybe better), you could also provide just a wrapper g that forwards to the real g : 或(也许更好),你也可以提供只是一个包装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. 实例。
Edit: Another option is using a temporary array: 编辑:另一个选项是使用临时数组:

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. 实例。 Note that the as_lvalue function is dangerous, the array still only lives until the end of the full expression (in this case g ), so be cautious when using it. 请注意, as_lvalue函数很危险,数组仍然只存在于完整表达式的末尾(在本例中为g ),因此在使用它时要小心。 The Alias is needed since just X[]{ ... } is not allowed due to the language grammar. 需要Alias ,因为语言语法只允许X[]{ ... }

If all of that's not possible, you'll need recursion to access all elements of the args pack. 如果所有这些都不可能,您将需要递归来访问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. 实例。

Edit: Inspired by ecatmur's comment, I employed the indices trick to make it work with just pack expansion and with f and g as-is, without altering them. 编辑:受到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);
}

Live example. 实例。

It's obvious: you don't use iteration but recursion. 很明显:你不使用迭代而是递归。 When dealing with variadic templates something recursive always comes in. Even when binding the elements to a std::tuple<...> using tie() it is recursive: It just happens that the recursive business is done by the tuple. 当处理可变参数模板时,总会有一些递归。甚至在使用tie()将元素绑定到std::tuple<...>它也是递归的:它恰好发生了递归业务由元组完成。 In your case, it seems you want something like this (there are probably a few typos but overall this should work): 在你的情况下,你似乎想要这样的东西(可能有一些拼写错误,但总的来说这应该有效):

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

I think you won't be able to use nargs to define the size of the array either: Nothing indicates to the compiler that it should be a constant expression. 我认为你将无法使用nargs来定义数组的大小:没有任何东西向编译器表明它应该是一个常量表达式。

Nice template as answer for first part of question: 好的模板作为问题第一部分的答案:

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

It's fairly simple to do with parameter pack expansion, even if you can't rewrite f to return the output parameter by value: 即使您无法重写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
}

It should be possible just to write 应该可以写

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

but it appears g++ (4.7.1 at least) has a bug where it fails to order the evaluation of brace-initializer-list parameters as class initialisers. 但似乎g ++(至少4.7.1)有一个错误,它无法将brace-initializer-list参数的评估命令为类初始化者。 Array initialisers are OK though; 数组初始化器虽然可以; see Sequencing among a variadic expansion for more information and examples. 有关更多信息和示例,请参见可变扩展中的排序

Live example . 实例


As an alternative, here's the technique mentioned by Xeo using a generated index pack; 作为替代方案,这是Xeo使用生成的索引包提到的技术; unfortunately it does require an extra function call and parameter, but it is reasonably elegant (especially if you happen to have an index pack generator lying around): 不幸的是它确实需要额外的函数调用和参数,但它相当优雅(特别是如果你碰巧有一个索引包生成器):

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

See C++11: I can go from multiple args to tuple, but can I go from tuple to multiple args? 参见C ++ 11:我可以从多个args转到元组,但是我可以从元组转到多个args吗? for more information. 欲获得更多信息。 Live example . 实例

Xeo is onto the right idea- you want to build some kind of "variadic iterator" that hides a lot of this nastiness from the rest of the code. Xeo是正确的想法 - 你想构建某种“可变迭代器”,它从其余的代码中隐藏了很多这种肮脏的东西。

I'd take the index stuff and hide it behind an iterator interface modeled after std::vector's, since a std::tuple is also a linear container for data. 我将索引内容隐藏在std :: vector之后的迭代器接口后面,因为std :: tuple也是数据的线性容器。 Then you can just re-use it all of your variadic functions and classes without having to have explicitly recursive code anywhere else. 然后,您可以重新使用它的所有可变参数函数和类,而无需在其他任何地方显式递归代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM