Chapter 4:Variadic Templates
第4章 可变参数模板
Since C++11, templates can have parameters that accept a variable number of template arguments. This feature allows the use of templates in places where you have to pass an arbitrary number of arguments of arbitrary types. A typical application is to pass an arbitrary number of parameters of arbitrary type through a class or framework. Another application is to provide generic code to process any number of parameters of any type.
从C++11开始,模板可以接受数量可变的模板参数。该特性允许我们在需要传入任意数量、任意类型参数的地方使用模板。经典的应用就是通过类或框架传递任意数量和任意类型的参数。另一个应用是提供泛型代码来处理任意数量和任意类型的参数。
4.1 Variadic Templates
4.1 可变参数模板
Template parameters can be defined to accept an unbounded number of template arguments. Templates with this ability are called variadic templates.
可以将模板参数定义为接受无限数量的模板参数。具有此能力的模板被称为可变参数模板。
4.1.1 Variadic Templates by Example
4.1.1 可变参数模板示例
For example, you can use the following code to call print() for a variable number of arguments of different types:
例如,你可以使用以下代码来调用数量可变且参数类型不同的print()函数:
#include <iostream> void print () { } template<typename T, typename… Types> void print (T firstArg, Types… args) { std::cout << firstArg << ‘\n’; //打印第1个参数 print(args…); //调用print()打印剩余的参数 }
If one or more arguments are passed, the function template is used, which by specifying the first argument separately allows printing of the first argument before recursively calling print() for the remaining arguments. These remaining arguments named args are a function parameter pack:
调用该函数模板时,如果传入一个或多个实参,则函数模板会指定和分离第一个参数并打印该参数,然后为其余的参数递归调用print()。这些剩余的名为args的参数,被称为“函数参数包”:
void print (T firstArg, Types… args);
using different “Types” specified by a template parameter pack:
使用模板参数包来指定不同的“类型”
template<typename T, typename… Types>
To end the recursion, the nontemplate overload of print() is provided, which is issued when the parameter pack is empty.
为了结束递归,需要提供重载的非模板类型print()函数,它将在参数包为空时被调用。
For example, a call such as
例如,如下调用
std::string s("world"); print (7.5, "hello", s);
would output the following:
将产生下面的输出:
7.5
hello
world
The reason is that the call first expands to
原因是调用会首先扩展成
print<double, char const*, std::string> (7.5, "hello", s);
with
由于
• firstArg having the value 7.5 so that type T is a double and
第1个参数为7.5,因此T类型被推导为double,以及
• args being a variadic template argument having the values "hello" of type char const* and "world" of type std::string.
args是一个可变模板参数,其值为“hello”(char const*类型)和 “world”(std::string类型)
After printing 7.5 as firstArg, it calls print() again for the remaining arguments, which then expands to:
将7.5作为firstArg打印之后,再次调用print()来打印其余的参数,然后展开为:
print<char const*, std::string> ("hello", s);
with
由于
• firstArg having the value "hello" so that type T is a char const* here。 and
firstArg为”hello”,因此这里T为char const*类型。同时,
• args being a variadic template argument having the value of type std::string.
args是一个可变模板参数,其类型为std::string。
After printing "hello" as firstArg, it calls print() again for the remaining arguments, which then expands to:
将”hello”作为firstArg打印之后,再次为剩余的参数调用print()函数,然后它将展开为:
print<std::string> (s);
with
由于
• firstArg having the value "world" so that type T is a std::string now。and
firstArg的值为“world”,因此,现在T的类型为std::string。同时,
• args being an empty variadic template argument having no value.
args是个空的可变模板参数,它没有值。
Thus, after printing "world" as firstArg, we calls print() with no arguments, which results in calling the nontemplate overload of print() doing nothing.
因此,在将“world”作为firstArg打印之后,我们调用print()函数而且不带参数。这将导致调用重载的非模板print()函数,该函数不执行任何操作。
4.1.2 Overloading Variadic and Nonvariadic Templates
4.1.2 可变参数和非可变参数模板的重载
Note that you can also implement the example above as follows:
注意,你也可以将上面的例子按如下实现:
#include <iostream> template<typename T> void print (T arg) { std::cout << arg << ‘\n’; /打印传递过来的参数 } template<typename T, typename… Types> void print (T firstArg, Types… args) { print(firstArg); // 为第1个参数调用call print() 函数 print(args…); // 为其余的参数调用print() }
That is, if two function templates only differ by a trailing parameter pack, the function template without the trailing parameter pack is preferred. Section C.3.1 on page 688 explains the more general overload resolution rule that applies here.
也就是说,如果两个重载的函数模板仅仅只有尾随参数包之差,则首选不带尾随参数包的模板函数(译注:例如,调用print()打印最后一个参数时,这两个模板会都被匹配,但首先会选择不带尾随的函数模板)。第688页的C.3.1节介绍了应用于这个地方的一些更通用的重载解决规则。
4.1.3 Operator sizeof…
4.1.3 sizeof…运算符
C++11 also introduced a new form of the sizeof operator for variadic templates: sizeof…. It expands to the number of elements a parameter pack contains. Thus,
C++11还为可变参数模板引入了sizeof运算符的新形式:sizeof…。它的功能扩展到可计算参数包所包含的元素个数。因此,
template<typename T, typename… Types> void print (T firstArg, Types… args) { std::cout << sizeof…(Types) <<’\n’; //打印剩余类型的数量 std::cout << sizeof…(args) << ‘\n’; //打印剩余参数的数量 … }
twice prints the number of remaining arguments after the first argument passed to print(). As you can see, you can call sizeof… for both template parameter packs and function parameter packs.
当第一个参数传递给print()后,会两次打印剩余参数的数量。如你所见,你可以为模板参数包(Types)和函数参数包(args)调用sizeof…。
This might lead us to think we can skip the function for the end of the recursion by not calling it in case there are no more arguments:
这可能会让我们认为,如果没有更多的参数,可以跳过调用“递归结束函数”来结束递归。
template<typename T, typename… Types> void print (T firstArg, Types… args) { std::cout << firstArg << ’\n’; if (sizeof…(args) > 0) { //如果sizeof…(args)==0时会出现错误 print(args…); // 没有声明无参的print()函数。 } }
However, this approach doesn’t work because in general both branches of all if statements in function templates are instantiated. Whether the instantiated code is useful is a run-time decision, while the instantiation of the call is a compile-timedecision. For this reason, if you call the print() function template for one (last) argument, the statement with the call of print(args…) still is instantiated for no argument, and if there is no function print() for no arguments provided, this is an error.
但是,这个方法是行不通的。因为通常会实例化函数模板中所有if语句的两个分支(译注,即>0和<=0)。实例化的代码是否有用是运行期的决定,而调用的实例化是编译期的决定。因此,如果你为一个(最后1个)实参调用print()函数模板,那么print(args…)的调用语句仍会实例化为无参函数,但如果没有提供print()无参版本,则会出错。(译注:即,当只剩最后一个参数,会实例化出一个只有1个参数的print()函数代码。这段代码中会包括sizeof…(args)==0的条件判断以及if的语句体部分,而if其后的语句中,需要调用print()无参函数,此时如果不提供这个函数,编译就会出错)
However, note that since C++17, a compile-time if is available, which achieves what was expected here with a slightly different syntax. This will be discussed in Section 8.5 on page 134.
然而,注意从C++17起,可以使用编译期的if(它在语法上略有不同),从而达到预期的效果。这将在第134页的8.5节中讨论)