Team LiB
Previous Section Next Section

16.1. Template Definitions

16.1. 模板定义

Let's imagine that we want to write a function to compare two values and indicate whether the first is less than, equal to, or greater than the second. In practice, we'd want to define several such functions, each of which could compare values of a given type. Our first attempt might be to define several overloaded functions:

假设想要编写一个函数比较两个值并指出第一个值是小于、等于还是大于第二个值。实践中,我们可能希望定义几个这样的函数,每一个可以比较一种给定类型的值,第一次尝试可能是定义几个重载函数:

     // returns 0 if the values are equal, -1 if v1 is smaller, 1 if v2 is smaller
     int compare(const string &v1, const string &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }
     int compare(const double &v1, const double &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

These functions are nearly identical: The only difference between them is the type of their parameters. The function body is the same in each function.

这些函数几乎相同,它们之间唯一的区别是形参的类型,每个函数的函数体是相同的。

Having to repeat the body of the function for each type that we compare is tedious and error-prone. More importantly, we need to know in advance all the types that we might ever want to compare. This strategy cannot work if we want to be able to use the function on types that we don't know about.

每个要比较的类型都需要重复函数的函数体,不仅麻烦而且容易出错。更重要的是,需要事先知道空间可能会比较哪些类型。如果希望将函数用于未知类型,这种策略就不起作用了。

16.1.1. Defining a Function Template

16.1.1. 定义函数模板

Rather than defining a new function for each type, we can define a single function template. A function template is a type-independent function that is used as a formula for generating a type-specific version of the function. For example, we might write a function template named compare, which would tell the compiler how to generate specific versions of compare for the types that we want to compare.

我们可以不用为每个类型定义一个新函数,而是只定义一个函数模板(function template)。函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。例如,可以编写名为 compare 的函数模板,它告诉编译器如何为我们想要比较的类型产生特定的 compare 版本。

The following is a template version of compare:

下面是 compare 的模板版本:

     // implement strcmp-like generic compare function
     // returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
     template <typename T>
     int compare(const T &v1, const T &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

A template definition starts with the keyword template followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by the less-than (<) and greater-than (>) tokens.

模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。

The template parameter list cannot be empty.

模板形参表不能为空。



Template Parameter List
模板形参表

The template parameter list acts much like a function parameter list. A function parameter list defines local variable(s) of a specified type but leaves those variables uninitialized. At run time, arguments are supplied that initialize the parameters.

模板形参表很像函数形参表,函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。

Analogously, template parameters represent types or values we can use in the definition of a class or function. For example, our compare function declares one type parameter named T. Inside compare, we can use the name T to refer to a type. Which actual type T represents is determined by the compiler based on how the function is used.

同样,模板形参表示可以在类或函数的定义中使用的类型或值。例如,compare 函数声明一个名为 T 的类型形参。在 compare 内部,可以使用名字 T 引用一个类型,T 表示哪个实际类型由编译器根据所用的函数而确定。

A template parameter can be a type parameter, which represents a type, or a nontype parameter, which represents a constant expression. A nontype parameter is declared following a type specifier. We'll see more about nontype parameters in Section 16.1.5 (p. 632). A type parameter is defined following the keyword class or typename. For example, class T is a type parameter named T. There is no difference between class and typename in this context.

模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。非类型形参跟在类型说明符之后声明,第 16.1.5 节将进一步介绍非类型形参。类型形参跟在关键字 classtypename 之后定义,例如,class T 是名为 T 的类型形参,在这里 classtypename 没有区别。

Using a Function Template
使用函数模板

When we use a function template, the compiler infers what template argument(s) to bind to the template parameter(s). Once the compiler determines the actual template argument(s), it instantiates an instance of the function template for us. Essentially, the compiler figures out what type to use in place of each type parameter and what value to use in place of each nontype parameter. Having deduced the actual template arguments, it generates and compiles a version of the function using those arguments in place of the corresponding template parameters. The compiler takes on the tedium of (re)writing the function for each type we use.

使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。实质上,编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。推导出实际模板实参后,编译器使用实参代替相应的模板形参产生编译该版本的函数。编译器承担了为我们使用的每种类型而编写函数的单调工作。

Given the calls

对于以下调用

     int main ()
     {
         // T is int;
         // compiler instantiates int compare(const int&, const int&)
         cout << compare(1, 0) << endl;
         // T is string;
         // compiler instantiates int compare(const string&, const string&)
         string s1 = "hi", s2 = "world";
         cout << compare(s1, s2) << endl;
         return 0;
     }

the compiler will instantiate two different versions of compare. The compiler will create one version that replaces T by int and a second version that uses string in place of T.

编译器将实例化 compare 的两个不同版本,编译器将用 int 代替 T 创建第一个版本,并用 string 代替 T 创建第二个版本。

inline Function Templates
inline 函数模板

A function template can be declared inline in the same way as a nontemplate function. The specifier is placed following the template parameter list and before the return type. It is not placed in front of the template keyword.

函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。

     // ok: inline specifier follows template parameter list
     template <typename T> inline T min(const T&, const T&);
     // error: incorrect placement of inline specifier
     inline template <typename T> T min(const T&, const T&);

Exercises Section 16.1.1

Exercise 16.1:

Write a template that returns the absolute value of its parameter. Call the template on values of at least three different types. Note: until we discuss how the compiler handles template instantiation in Section 16.3 (p. 643), you should put each template definition and all uses of that template in the same file.

编写一个模板返回形参的绝对值。至少用三种不同类型的值调用模板。注意:在第 16.3 节讨论编译器怎样处理模板实例化之前,你应该将每个模板定义和该模板的所有使用放在同一文件中。

Exercise 16.2:

Write a function template that takes a reference to an ostream and a value, and writes the value to the stream. Call the function on at least four different types. Test your program by writing to cout, to a file, and to a stringstream.

编写一个函数模板,接受一个 ostream 引用和一个值,将该值写入流。用至少四种不同类型调用函数。通过写至 cout、写至文件和写至 stringstream 来测试你的程序。

Exercise 16.3:

When we called compare on two strings, we passed two string objects, which we initialized from string literals. What would happen if we wrote:

当调用两个 string 对象的 compare 时,传递用字符串字面值初始化的两个 string 对象。如果编写以下代码会发生什么?

     compare ("hi", "world");


16.1.2. Defining a Class Template

16.1.2. 定义类模板

Just as we can define function templates, we can also define class templates.

就像可以定义函数模板一样,也可以定义类模板。

To illustrate class templates, we'll implement our own version of the standard library queue (Section 9.7, p. 348) class. User programs ought to use the standard queue class, not the one we define here.

为了举例说明类模板,我们将为标准库 queue 类(第 9.7 节)实现一个自己的版本。用户程序应使用标准的 queue 类,而不是我们这里定义的这个 Queue 类。



Our Queue must be able to hold objects of different types, so we'll define it as a class template. The operations our Queue will support are a subset of the interface of the standard queue:

我们自定义的 Queue 类必须能够支持不同类型的对象,所以将它定义为类模板Queue 类将支持的操作是标准 queue 类接口的子集:

  • push to add an item to the back of the queue

    push 操作,在队尾增加一项

  • pop to remove the item at the head of the queue

    pop 操作,从队头删除一项

  • front to return a reference to the element at the head of the queue

    front 操作,返回队头元素的引用

  • empty to indicate whether there are any elements in the queue

    empty 操作,指出队列中是否有元素

We'll look at how we might implement our Queue in Section 16.4 (p. 647), but we can start by defining its interface:

第 16.4 节将介绍怎样实现 Queue 类,这里先定义它的接口:

     template <class Type> class Queue {
     public:
         Queue ();                // default constructor
         Type &front ();          // return element from head of Queue
         const Type &front () const;
         void push (const Type &); // add element to back of Queue
         void pop();              // remove element from head of Queue
         bool empty() const;      // true if no elements in the Queue
     private:
         // ...
     };

A class template is a template, so it must begin with the keyword template followed by a template parameter list. Our Queue template takes a single template type parameter named Type.

类模板也是模板,因此必须以关键字 template 开头,后接模板形参表。Queue 模板接受一个名为 Type 的模板类型形参。

With the exception of the template parameter list, the definition of a class template looks like any other class. A class template may define data, function, and type members; it may use access labels to control access to those members; it defines constructors and destructors; and so on. In the definition of the class and its members, we can use the template parameters as stand-ins for types or values that will be supplied when the class is used.

除了模板形参表外,类模板的定义看起来与任意其他类问相似。类模板可以定义数据成员、函数成员和类型成员,也可以使用访问标号控制对成员的访问,还可以定义构造函数和析构函数等等。在类和类成员的定义中,可以使用模板形参作为类型或值的占位符,在使用类时再提供那些类型或值。

For example, our Queue template has one template type parameter. We can use that parameter anywhere a type name can be used. In this template definition, we use Type to name the return type from the overloaded front operations and as the parameter type for the push operation.

例如,Queue 模板有一个模板类型形参,可以在任何可以使用类型名字的地方使用该形参。在这个模板定义中,用 Type 指定重载 front 操作的返回类型以及作为 push 操作的形参类型。

Using a Class Template
使用类模板

In contrast to calling a function template, when we use a class template, we must explicitly specify arguments for the template parameters:

与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:

     Queue<int> qi;                 // Queue that holds ints
     Queue< vector<double> > qc;    // Queue that holds vectors of doubles
     Queue<string> qs;              // Queue that holds strings

The compiler uses the arguments to instantiate a type-specific version of the class. Essentially, the compiler rewrites our Queue class replacing Type by the specified actual type provided by the user. In this case, the compiler will instantiate three classes: a version of Queue with Type replaced by int, a second Queue class that uses vector<double> in place of Type, and a third that replaces Type by string.

编译器使用实参来实例化这个类的特定类型版本。实质上,编译器用用户提供的实际特定类型代替 Type,重新编写 Queue 类。在这个例子中,编译器将实例化三个 Queue 类:第一个用 int 代替 Type,第二个用 vector<double> 代替 Type,第三个用 string 代替 Type

Exercises Section 16.1.2

Exercise 16.4:

What is a function template? What is a class template?

什么是函数模板?什么是类模板?

Exercise 16.5:

Define a function template to return the larger of two values.

定义一个函数模板,返回两个值中较大的一个。

Exercise 16.6:

Similar to our a simplified version of queue, write a class template named List that is a simplified version of the standard list class.

类似于我们的 queue 简化版本,编写一个名为 List 的类模板,作为标准 list 类的简化版本。


16.1.3. Template Parameters

16.1.3. 模板形参

As with a function parameter, the name chosen by the programmer for a template parameter has no intrinsic meaning. In our example, we named compare's template type parameter T, but we could have named it anything:

像函数形参一样,程序员为模板形参选择的名字没有本质含义。在我们的例子中,将 compare 的模板类型形参命名为 T,但也可以将它命名为任意名字:

     // equivalent template definition
     template <class Glorp>
     int compare(const Glorp &v1, const Glorp &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

This code defines the same compare template as before.

该代码定义的 compare 模板与前面一样。

The only meaning we can ascribe to a template parameter is to distinguish whether the parameter is a type parameter or a nontype parameter. If it is a type parameter, then we know that the parameter represents an as yet unknown type. If it is a nontype parameter, we know it is an as yet unknown value.

可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参。如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。

When we wish to use the type or value that a template parameter represents, we use the same name as the corresponding template parameter. For example, all references to Glorp in the compare function template will be resolved to the same type when the function is instantiated.

如果希望使用模板形参所表示的类型或值,可以使用与对应模板形参相同的名字。例如,compare 函数中所有的 Glorp 引用将在该函数被实例化时确定为同一类型。

Template Parameter Scope
模板形参作用域

The name of a template parameter can be used after it has been declared as a template parameter and until the end of the template declaration or definition.

模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。

Template parameters follow normal name-hiding rules. A template parameter with the same name as an object, function, or type declared in global scope hides the global name:

模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字:

     typedef double T;
     template <class T> T calc(const T &a, const T &b)
     {
          // tmp has the type of the template parameter T
          // not that of the global typedef
          T tmp = a;
          // ...
          return tmp;
     }

The global typedef that defines T as double is hidden by the type parameter named T. Thus, tmp is not a double. Instead, the type of tmp is whatever type gets bound to the template parameter T.

T 定义为 double 的全局类型型别名将被名为 T 的类型形参所屏蔽,因此,tmp 不是 double 型,相反,tmp 的类型是绑定到模板形参的任意类型。

Restrictions on the Use of a Template Parameter Name
使用模板形参名字的限制

A name used as a template parameter may not be reused within the template:

用作模板形参的名字不能在模板内部重用。

     template <class T> T calc(const T &a, const T &b)
     {
         typedef double T; // error: redeclares template parameter T
         T tmp = a;
         // ...
         return tmp;
     }

This restriction also means that the name of a template parameter can be used only once within the same template parameter list:

这一限制还意味着模板形参的名字只能在同一模板形参表中使用一次:

     // error: illegal reuse of template parameter name V
     template <class V, class V> V calc(const V&, const V&) ;

Of course, just as we can reuse function parameter names, the name of a template parameter can be reused across different templates:

当然,正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:

     // ok: reuses parameter type name across different templates
     template <class T> T calc (const T&, const T&) ;
     template <class T> int compare(const T&, const T&) ;

Template Declarations
模板声明

As with any other function or class, we can declare a template without defining it. A declaration must indicate that the function or class is a template:

像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函数或类是一个模板:

// declares compare but does not define it
     template <class T> int compare(const T&, const T&) ;

The names of the template parameters need not be the same across declarations and the definition of the same template:

同一模板的声明和定义中,模板形参的名字不必相同。

     // all three uses of calc refer to the same function template
     // forward declarations of the template
     template <class T> T calc(const T&, const T&) ;
     template <class U> U calc(const U&, const U&) ;
     // actual definition of the template
     template <class Type>
     Type calc(const Type& a, const Type& b) { /* ... */ }

Each template type parameter must be preceded either by the keyword class or typename; each nontype parameter must be preceded by a type name. It is an error to omit the keyword or a type specifier:

每个模板类型形参前面必须带上关键字 classtypename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:

     // error: must precede U by either typename or class
     template <typename T, U> T calc (const T&, const U&) ;

Exercises Section 16.1.3

Exercise 16.7:

Explain each of the following function template definitions and identify whether any are illegal. Correct each error that you find.

解释下面每个函数模板的定义并指出是否有非法的。改正所发现的错误。

     (a) template <class T, U, typename V> void f1(T, U, V) ;
     (b) template <class T> T f2(int &T) ;
     (c) inline template <class T> T foo(T, unsigned int*) ;
     (d) template <class T> f4 (T, T) ;
     (e) typedef char Ctype ;
         template <typename Ctype> Ctype f5(Ctype a) ;

Exercise 16.8:

Explain which, if any, of the following declarations are errors and why.

如果有,解释下面哪些声明是错误的说明为什么。

     (a) template <class Type> Type bar(Type, Type) ;
         template <class Type> Type bar(Type, Type) ;
     (b) template <class T1, class T2> void bar(T1, T2) ;
         template <class C1, typename C2> void bar(C1, C2) ;

Exercise 16.9:

Write a template that acts like the library find algorithm. Your template should take a single type parameter that will name the type for a pair of iterators that should be parameters to the function. Use your function to find a given value in a vector<int> and in a list<string>.

编写行为类似于标准库中 find 算法的模板。你的模板应接受一个类型形参,该形参指定函数形参(一对迭代器)的类型。使用你的函数在 vector<int>list<string> 中查找给定值。


16.1.4. Template Type Parameters

16.1.4. 模板类型形参

Type parameters consist of the keyword class or the keyword typename followed by an identifier. In a template parameter list, these keywords have the same meaning: They indicate that the name that follows represents a type.

类型形参由关键字 classtypename 后接说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。

A template type parameter can be used as a type specifier anywhere in the template, in exactly the same way as a built-in or class type specifier. In particular, it can be used to name the return type or a function parameter type, and for variable declarations or casts inside the function body:

模板类型形参可作为类型说明符在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。具体而言,它可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。

     // ok: same type used for the return type and both parameters
     template <class T> T calc (const T& a, const T& b)
     {
          // ok: tmp will have same type as the parameters & return type
          T tmp = a;
          // ...
          return tmp;
     }

Distinction Between typename and class
typenameclass 的区别

In a function template parameter list, the keywords typename and class have the same meaning and can be used interchangeably. Both keywords can be used in the same template parameter list:

在函数模板形参表中,关键字 typenameclass 具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:

     // ok: no distinction between typename and class in template parameter list
     template <typename T, class U> calc (const T&, const U&);

It may seem more intuitive to use the keyword typename instead of the keyword class to designate a template type parameter; after all, we can use built-in (nonclass types) types as the actual type parameter. Moreover, typename more clearly indicates that the name that follows is a type name. However, the keyword typename was added to C++ as part of Standard C++, so older programs are more likely to use the keyword class exclusively.

使用关键字 typename 代替关键字 class 指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename 更清楚地指明后面的名字是一个类型名。但是,关键字 typename 是作为标准 C++ 的组成部分加入到 C++ 中的,因此旧的程序更有可能只用关键字 class

Designating Types inside the Template Definition
在模板定义内部指定类型

In addition to defining data or function members, a class may define type members. For example, the library container classes define various types, such as size_type, that allow us to use the containers in a machine-independent way. When we want to use such types inside a function template, we must tell the compiler that the name we are using refers to a type. We must be explicit because the compiler (and a reader of our program) cannot tell by inspection when a name defined by a type parameter is a type or a value. As an example, consider the following function:

除了定义数据成员或函数成员之外,类还可以定义类型成员。例如,标准库的容器类定义了不同的类型,如 size_type,使我们能够以独立于机器的方式使用容器。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。例如,考虑下面的函数:

     template <class Parm, class U>
     Parm fcn(Parm* array, U value)
     {
         Parm::size_type * p; // If Parm::size_type is a type, then a declaration
                              // If Parm::size_type is an object, then multiplication
     }

We know that size_type must be a member of the type bound to Parm, but we do not know whether size_type is the name of a type or a data member. By default, the compiler assumes that such names name data members, not types.

我们知道 size_type 必定是绑定到 Parm 的那个类型的成员,但我们不知道 size_type 是一个类型成员的名字还是一个数据成员的名字,默认情况下,编译器假定这样的名字指定数据成员,而不是类型。

If we want the compiler to treat size_type as a type, then we must explicitly tell the compiler to do so:

如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做:

     template <class Parm, class U>
     Parm fcn(Parm* array, U value)
     {
         typename Parm::size_type * p; // ok: declares p to be a pointer
     }

We tell the compiler to treat a member as a type by prefixing uses of the member name with the keyword typename. By writing typename Parm::size_type we say that member size_type of the type bound to Parm is the name of a type. Of course, this declaration puts an obligation on the types used to instantiate fcn: Those types must have a member named size_type that is a type.

通过在成员名前加上关键字 typename 作为前缀,可以告诉编译器将成员当作类型。通过编写 typename parm::size_type,指出绑定到 Parm 的类型的 size_type 成员是类型的名字。当然,这一声明给用实例化 fcn 的类型增加了一个职责:那些类型必须具有名为 size_type 的成员,而且该成员是一个类型。

If there is any doubt as to whether typename is necessary to indicate that a name is a type, it is a good idea to specify it. There is no harm in specifying typename before a type, so if the typename was unnecessary, it won't matter.

如果拿不准是否需要以 typename 指明一个名字是一个类型,那么指定它是个好主意。在类型之前指定 typename 没有害处,因此,即使 typename 是不必要的,也没有关系。



Exercises Section 16.1.4

Exercise 16.10:

What, if any, are the differences between a type parameter that is declared as a typename and one that is declared as a class?

声明为 typename 的类型形参与声明为 class 的类型形参有区别吗?区别在哪里?

Exercise 16.11:

When must typename be used?

何时必须使用 typename

Exercise 16.12:

Write a function template that takes a pair of values that represent iterators of unknown type. Find the value that occurs most frequently in the sequence.

编写一个函数模板,接受表示未知类型迭代器的一对值,找出在序列中出现得最频繁的值。

Exercise 16.13:

Write a function that takes a reference to a container and prints the elements in that container. Use the container's size_type and size members to control the loop that prints the elements.

编写一个函数,接受一个容器的引用并打印该容器的元素。使用容器的 size_typesize 成员控制打印元素的循环。

Exercise 16.14:

Rewrite the function from the previous exercise to use iterators returned from begin and end to control the loop.

重新编写上题的函数,使用从 beginend 返回的迭代器来控制循环。


16.1.5. Nontype Template Parameters

16.1.5. 非类型模板形参

A template parameter need not be a type. In this section we'll look at nontype parameters as used by function templates. We'll look at nontype parameters for class templates in Section 16.4.2 (p. 655) after we've seen more about how class templates are implemented.

模板形参不必都是类型。本节将介绍函数模板使用的非类型形参。在介绍了类模板实现的更多内容之后,第 16.4.2 节将介绍类模板的非类型形参。

Nontype parameters are replaced by values when the function is called. The type of that value is specified in the template parameter list. For example, the following function template declares array_init as a function template with one type and one nontype template parameter. The function itself takes a single parameter, which is a reference to an array (Section 7.2.4, p. 240):

在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。例如,下面的函数模板声明了 array_init 是一个含有一个类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用(第 7.2.4 节):

     // initialize elements of an array to zero
     template <class T, size_t N> void array_init(T (&parm)[N])
     {
         for (size_t i = 0; i != N; ++i) {
             parm[i] = 0;
         }
     }

A template nontype parameter is a constant value inside the template definition. A nontype parameter can be used when constant expressions are requiredfor example, as we do hereto specify the size of an array.

模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度。

When array_init is called, the compiler figures out the value of the nontype parameter from the array argument:

当调用 array_init 时,编译器从数组实参计算非类型形参的值:

     int x[42];
     double y[10];
     array_init(x);  // instantiates array_init(int(&)[42]
     array_init(y);  // instantiates array_init(double(&)[10]

The compiler will instantiate a separate version of array_init for each kind of array used in a call to array_init. For the program above, the compiler instantiates two versions of array_init: The first instance has its parameter bound to int[42], and in the other, that parameter is bound to double[10].

编译器将为 array_init 调用中用到的每种数组实例化一个 array_init 版本。对于上面的程序,编译器将实例化 array_init 的两个版本:第一个实例的形参绑定到 int[42],另一个实例中的形参绑定到 double[10]

Type Equivalence and Nontype Parameters
类型等价性与非类型形参

Expressions that evaluate to the same value are considered equivalent template arguments for a template nontype parameter. The following calls to array_init both refer to the same instantiation, array_init<int, 42>:

对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。下面的两个 array_init 调用引用的是相同的实例—— array_init<int, 42>

     int x[42];
     const int sz = 40;
     int y[sz + 2];
     array_init(x);  // instantiates array_init(int(&)[42])
     array_init(y);  // equivalent instantiation

Exercises Section 16.1.5

Exercise 16.15:

Write a function template that can determine the size of an array.

编写可以确定数组长度的函数模板。

Exercise 16.16:

Rewrite the printValues function from page 240 as a function template that could be used to print the contents of arrays of varying sizes.

第 7.2.4 节printValues 函数重新编写为可用于打印不同长度数组内容的函数模板。


16.1.6. Writing Generic Programs

16.1.6. 编写泛型程序

When we write a template, the code may not be overtly type-specific, but template code always makes some assumptions about the types that will be used. For example, although our compare function is technically valid for any type, in practice the instantiated version might be illegal.

编写模板时,代码不可能针对特定类型,但模板代码总是要对将使用的类型做一些假设。例如,虽然 compare 函数从技术上说任意类型都是有效的,但实际上,实例化的版本可能是非法的。

Whether the generated program is legal depends on the operations used in the function and the operations supported by the type or types used. Our compare function has has three statements:

产生的程序是否合法,取决于函数中使用的操作以及所用类型支持的操作。compare 函数有三条语句:

     if (v1 < v2) return -1; // < on two objects of type T
     if (v2 < v1) return 1;  // < on two objects of type T
     return 0;               // return int; not dependent on T

The first two statements contain code that implicitly depends on the parameter type. The if tests use the < operator on the parameters. The type of those parameters isn't known until the compiler sees a call to compare and T is bound to an actual type. Which < operator is used depends entirely on the argument type.

前两条语句包含隐式依赖于形参类型的代码,if 测试对形参使用 < 操作符,直到编译器看见 compare 调用并且 T 绑定到一个实际类型时,才知道形参的类型,使用哪个 < 操作符完全取决于实参类型。

If we call compare on an object that does not support the < operator, then the call will be invalid:

如果用不支持 < 操作符的对象调用 compare,则该调用将是无效的:

     Sales_item item1, item2;
     // error: no < on Sales_item
     cout << compare(item1, item2) << endl;

The program is in error. The Sales_item type does not define the < operator, so the program won't compile.

程序会出错。Sales_item 类型没有定义 < 操作符,所以该程序不能编译。

The operations performed inside a function template constrains the types that can be used to instantiate the function. It is up to the programmer to guarantee that the types used as the function arguments actually support any operations that are used, and that those operations behave correctly in the context in which the template uses them.

在函数模板内部完成的操作限制了可用于实例化该函数的类型。程序员的责任是,保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用哪些操作的环境中那些操作运行正常。



Writing Type-Independent Code
编写独立于类型的代码

The art of writing good generic code is beyond the scope of this language primer. However, there is one overall guideline that is worth noting.

编写良好泛型代码的技巧超出了本书的范围,但是,有个一般原则值得注意。

When writing template code, it is useful to keep the number of requirements placed on the argument types as small as possible.

编写模板代码时,对实参类型的要求尽可能少是很有益的。



Simple though it is, our compare function illustrates two important principles for writing generic code:

虽然简单,但它说明了编写泛型代码的两个重要原则:

  • The parameters to the template are const references.

    模板的形参是 const 引用。

  • The tests in the body use only < comparisons.

    函数体中的测试只用 < 比较。

By making the parameters const references, we allow types that do not allow copying. Most typesincluding the built-in types and, except for the IO types, all the library types we've useddo allow copying. However, there can be class types that do not allow copying. By making our parameters const references, we ensure that such types can be used with our compare function. Moreover, if compare is called with large objects, then this design will also make the function run faster.

通过将形参设为 const 引用,就可以允许使用不允许复制的类型。大多数类型(包括内置类型和我们已使用过的除 IO 类型之外的所有标准库的类型)都允许复制。但是,也有不允许复制的类类型。将形参设为 const 引用,保证这种类型可以用于 compare 函数,而且,如果有比较大的对象调用 compare,则这个设计还可以使函数运行得更快。

Some readers might think it would be more natural for the comparisons to be done using both the < and > operators:

一些读者可能认为使用 <> 操作符两者进行比较会更加自然:

     // expected comparison
     if (v1 < v2) return -1;
     if (v1 > v2) return 1;
     return 0;

However, by writing the code as

但是,将代码编写为

     // expected comparison
     if (v1 < v2) return -1;
     if (v2 < v1) return 1; // equivalent to v1 > v2
     return 0;

we reduce the requirements on types that can be used with our compare function. Those types must support <, but they need not also support >.

可以减少对可用于 compare 函数的类型的要求,这些类型必须支持 <,但不必支持 >

Exercises Section 16.1.6

Exercise 16.17:

In the "Key Concept" box on page 95, we noted that as a matter of habit C++ programmers prefer using != to using <. Explain the rationale for this habit.

第 3.3.2 节的“关键概念”中,我们注意到,C++ 程序员习惯于使用 != 而不用 <,解释这一习惯的基本原理。

Exercise 16.18:

In this section we noted that we deliberately wrote the test in compare to avoid requiring a type to have both the < and > operators. On the other hand, we tend to assume that types will have both == and !=. Explain why this seeming discrepancy in treatment actually reflects good programming style.

本节中我们提到应该慎重地编写 compare 中的信息论以避免要求类型同时具有 <> 操作符,另一方面,往往假定类型既有 == 又有 !=。解释为什么这一看似不一致的处理实际上反映了良好的编程风格。


Caution: Compile-Time Errors at Link-Time

警告:链接时的编译时错误

In general, when compiling a template, there are three stages during which the compiler might flag an error: The first is when we compile the template definition itself. The compiler generally can't find many errors at this stage. Syntax errors, such as forgetting a semicolon or misspelling a variable name, can be detected.

一般而言,编译模板时,编译器可能会在三个阶段中标识错误:第一阶段是编译模板定义本身时。在这个阶段中编译器一般不能发现许多错误,可以检测到诸如漏掉分号或变量名拼写错误一类的语法错误。

The second error-detection time is when the compiler sees a use of the template. At this stage, there is still not much the compiler can check. For a call to a function template, many compilers check only that the number and types of the arguments are appropriate. The compiler can detect that there are too many or too few arguments. It can also detect whether two arguments that are supposed to have the same type do so. For a class template, the compiler can check that the right number of template arguments are provided but not much else.

第二个错误检测时间是在编译器见到模板的使用时。在这个阶段,编译器仍没有很多检查可做。对于函数模板的调用,许多编译器只检查实参的数目和类型是否恰当,编译器可以检测到实参太多或太少,也可以检测到假定类型相同的两个实参是否真地类型相同。对于类模板,编译器可以检测提供的模板实参的正确数目。

The third time when errors are generated is during instantiation. It is only then that type-related errors can be found. Depending on how the compiler manages instantiation, which we'll cover on page 643, these errors may be reported at link time.

产生错误的第三个时间是在实例化的时候,只有在这个时候可以发现类型相关的错误。根据编译器管理实例化的方式(将在第 16.3 节讨论),有可能在链接时报告这些错误。

It is important to realize that when we compile a template definition, we do not know much about how valid the program is. Similarly, we may obtain compiler errors even after we have successfully compiled each file that uses the template. It is not uncommon to detect errors only during instantiation, which may happen at link-time.

重要的是,要认识到编译模板定义的时候,对程序是否有效所知不多。类似地,甚至可能会在已经成功编译了使用模板的每个文件之后出现编译错误。只在实例化期间检测错误的情况很少,错误检测可能发生在链接时。


Team LiB
Previous Section Next Section