C++ 可变模板包扩展
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25680461/
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
Variadic template pack expansion
提问by Viacheslav Dronov
I am trying to learn variadic templates and functions. I can't understand why this code doesn't compile:
我正在尝试学习可变参数模板和函数。我不明白为什么这段代码不能编译:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args)
{
(bar(args)...);
}
int main()
{
foo2(1, 2, 3, "3");
return 0;
}
When I compile it fails with the error:
当我编译它失败并出现错误:
Error C3520: 'args': parameter pack must be expanded in this context
错误 C3520:“args”:必须在此上下文中扩展参数包
(in function foo2
).
(在函数中foo2
)。
回答by T.C.
One of the places where a pack expansion can occur is inside a braced-init-list. You can take advantage of this by putting the expansion inside the initializer list of a dummy array:
可能发生包扩展的地方之一是在支撑初始化列表中。您可以通过将扩展放在虚拟数组的初始化列表中来利用这一点:
template<typename... Args>
static void foo2(Args &&... args)
{
int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}
To explain the content of the initializer in more detail:
更详细地解释初始化器的内容:
{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
| | | | |
| | | | --- pack expand the whole thing
| | | |
| | --perfect forwarding --- comma operator
| |
| -- cast to void to ensure that regardless of bar()'s return type
| the built-in comma operator is used rather than an overloaded one
|
---ensure that the array has at least one element so that we don't try to make an
illegal 0-length array when args is empty
Demo.
演示。
An important advantage of expanding in {}
is that it guarantees left-to-right evaluation.
扩展的一个重要优势{}
是它保证了从左到右的评估。
With C++17 fold expressions, you can just write
使用 C++17折叠表达式,您只需编写
((void) bar(std::forward<Args>(args)), ...);
回答by Angew is no longer proud of SO
Parameter packs can only be expanded in a strictly-defined list of contexts, and operator ,
is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator ,
.
参数包只能在严格定义的上下文列表中展开,运算符,
不是其中之一。换句话说,不可能使用包扩展来生成由一系列由 operator 分隔的子表达式组成的表达式,
。
The rule of thumb is "Expansion can generate a listof ,
-separated patterns where ,
is a listdelimiter." Operator ,
does not construct a list in the grammar sense.
经验法则是“扩展可以生成一个分隔模式列表,,
其中,
是列表分隔符。” 运算符,
不构造语法意义上的列表。
To call a function for each argument, you can use recursion (which is the primary tool in the variadic template programmer's box):
要为每个参数调用一个函数,您可以使用递归(这是可变参数模板程序员框中的主要工具):
template <typename T>
void bar(T t) {}
void foo2() {}
template <typename Car, typename... Cdr>
void foo2(Car car, Cdr... cdr)
{
bar(car);
foo2(cdr...);
}
int main()
{
foo2 (1, 2, 3, "3");
}
回答by CoffeeandCode
SHAMELESS COPY [approved by its source]
Parameter packs can only be expanded in a strictly-defined list of contexts, and operator ,
is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator ,
.
参数包只能在严格定义的上下文列表中展开,运算符,
不是其中之一。换句话说,不可能使用包扩展来生成由一系列由 operator 分隔的子表达式组成的表达式,
。
The rule of thumb is "Expansion can generate a list of ,
-separated patterns where ,
is a list delimiter." Operator ,
does not construct a list in the grammar sense.
经验法则是“扩展可以生成一个分隔,
模式列表,其中,
是列表分隔符。” 运算符,
不构造语法意义上的列表。
To call a function for each argument, you can use recursion (which is the primary tool in the variadic template programmer's box):
要为每个参数调用一个函数,您可以使用递归(这是可变参数模板程序员框中的主要工具):
#include <utility>
template<typename T>
void foo(T &&t){}
template<typename Arg0, typename Arg1, typename ... Args>
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
foo(std::forward<Arg0>(arg0));
foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}
auto main() -> int{
foo(1, 2, 3, "3");
}
USEFUL NON-COPIED INFO
有用的非复制信息
Another thing you probably haven't seen in this answer is use of the &&
specifier and std::forward
. In C++, the &&
specifier can mean one of 2 things: rvalue-references, or universal references.
您在此答案中可能还没有看到的另一件事是使用说明&&
符 和std::forward
。在 C++ 中,说明&&
符可以表示两件事之一:右值引用或通用引用。
I won't go into rvalue-references, but to somebody working with variadic templates; universal references are a god-send.
我不会讨论右值引用,而是讨论使用可变参数模板的人;通用参考是天赐之物。
Perfect Forwarding
完美转发
One of the uses of std::forward
and universal references are perfect forwarding of types to other functions.
std::forward
通用引用的用途之一是将类型完美转发到其他函数。
In your example, if we pass an int&
to foo2
it will be automatically demoted to int
because of the signature of the generated foo2
function after template deduction and if you wanted to then forward this arg
to another function that would modify it by reference, you will get undesired results (the variable won't be changed) because foo2
will be passing a reference to the temporary created by passing an int
to it. To get around this, we specify a forwarding function to take anytype of referenceto a variable (rvalue orlvalue). Then, to be sure that we pass the exact type passed in the forwarding function we use std::forward
, then and onlythen do we allow the demoting of types; because we are now at the point where it matters most.
在您的示例中,如果我们传递int&
给foo2
它,int
由于foo2
模板推导后生成的函数的签名,它将自动降级为,如果您想将其转发arg
给另一个将通过引用修改它的函数,您将得到不想要的结果(该变量不会更改),因为foo2
将传递对通过将 an 传递int
给它创建的临时对象的引用。为了解决这个问题,我们指定了一个转发函数来获取对变量(右值或左值)的任何类型的引用。然后,为了确保我们传递在我们使用的转发函数中传递的确切类型,然后并且仅std::forward
那么我们是否允许降级类型;因为我们现在正处于最重要的时刻。
If you need to, read more on universal referencesand perfect forwarding; Scott Meyers is pretty great as a resource.
回答by Guillaume Racicot
The C++17 solution to this is really close to your expected code:
对此的 C++17 解决方案非常接近您的预期代码:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args) {
(bar(args), ...);
}
int main() {
foo2(1, 2, 3, "3");
return 0;
}
This expand the pattern with the comma operator between every expression
这在每个表达式之间使用逗号运算符扩展模式
// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));
回答by Lorah Attkins
You can use make_tuple
for pack expansion as it introduces a context where the ,
sequence produced by an expansion is valid
您可以使用make_tuple
包扩展,因为它引入了一个上下文,其中,
扩展产生的序列是有效的
make_tuple( (bar(std::forward<Args>(args)), 0)... );
Now, I suspect the unused/unnamed/temporary tuple of zeroes that's produced is detectable by the compiler and optimized away.
现在,我怀疑生成的未使用/未命名/临时零元组可以被编译器检测到并被优化掉。
回答by lama12345
This is a full example, based on the answers here.
这是一个完整的例子,基于这里的答案。
Example to reproduce console.log
as seen in JavaScript:
console.log
在 JavaScript 中看到的重现示例:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Filename e.g. js_console.h
:
文件名例如js_console.h
:
#include <iostream>
#include <utility>
class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};