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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12030538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:47:54  来源:igfitidea点击:

Calling a function for each variadic template argument and an array

c++templatesc++11variadic-templates

提问by Andrew Tomazos

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 hthat does this:

我需要编写一个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 而不是作为输出参数,所以我什至不需要上面的 fv )

回答by Xeo

You could refactor or wrap fto return a new Xinstead 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 gto 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 gthat forwards to the real g:

活生生的例子。或者(也许更好),您也可以仅提供一个g转发到 real的包装器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_lvaluefunction is dangerous, the array still only lives until the end of the full expression (in this case g), so be cautious when using it. The Aliasis needed since just X[]{ ... }is not allowed due to the language grammar.

活生生的例子。请注意,该as_lvalue函数是危险的,数组仍然只存在到完整表达式的结尾(在本例中g),因此在使用它时要小心。该Alias是必要的,因为刚刚X[]{ ... }是不是由于语言的语法允许的。

If all of that's not possible, you'll need recursion to access all elements of the argspack.

如果所有这些都不可能,您将需要递归来访问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 trickto make it work with just pack expansion and with fand gas-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.

活生生的例子。

回答by Dietmar Kühl

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. In your case, it seems you want something like this (there are probably a few typos but overall this should work):

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

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 nargsto define the size of the array either: Nothing indicates to the compiler that it should be a constant expression.

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

回答by Victor Laskin

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

回答by ecatmur

It's fairly simple to do with parameter pack expansion, even if you can't rewrite fto 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. Array initialisers are OK though; see Sequencing among a variadic expansionfor more information and examples.

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

Live example.

活生生的例子



As an alternative, here's the technique mentioned by Xeo using a generated index pack; 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):

作为替代方案,这里是 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)...);
}

See C++11: I can go from multiple args to tuple, but can I go from tuple to multiple args?for more information. Live example.

请参阅C++11:我可以从多个 args 转到 tuple,但是我可以从 tuple 转到多个 args 吗?想要查询更多的信息。 活生生的例子

回答by Zack Yezek

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. Then you can just re-use it all of your variadic functions and classes without having to have explicitly recursive code anywhere else.

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