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
Calling a function for each variadic template argument and an array
提问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 h
that 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 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));
}
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
转发到 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_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. The Alias
is 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 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);
}
Edit:Inspired by ecatmur's comment, I employed the indices trickto make it work with just pack expansion and with f
and g
as-is, without altering them.
编辑:受 ecatmur 评论的启发,我采用了索引技巧,使其仅适用于包扩展以及使用f
和g
原样,而无需更改它们。
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);
}
回答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 nargs
to 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 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. Array initialisers are OK though; see Sequencing among a variadic expansionfor more information and examples.
但似乎 g++(至少 4.7.1)有一个错误,它无法将大括号初始化器列表参数的评估排序为类初始化器。数组初始值设定项是可以的;有关更多信息和示例,请参阅可变参数扩展中的排序。
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 也是数据的线性容器。然后,您可以在所有可变参数函数和类中重新使用它,而无需在其他任何地方显式地使用递归代码。