Team LiB
Previous Section Next Section

16.2. Instantiation

16.2. 实例化

A template is a blueprint; it is not itself a class or a function. The compiler uses the template to generate type-specific versions of the specified class or function. The process of generatng a type-specific instance of a template is known as instantiation. The term reflects the notion that a new "instance" of the template type or function is created.

模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程称为实例化,这个术语反映了创建模板类型或模板函数的新“实例”的概念。

A template is instantiated when we use it. A class template is instantiated when we refer to the an actual template class type, and a function template is instantiated when we call it or use it to initialize or assign to a pointer to function.

模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。

Instantiating a Class

类的实例化

When we write

当编写

     Queue<int> qi;

the compiler automatially creates a class named Queue<int>. In effect, the compiler creates the Queue<int> class by rewriting the Queue template, replacing every occurrence of the template parameter Type by the type int. The instantiated class is as if we had written:

编译器自动创建名为 Queue 的类。实际上,编译器通过重新编写 Queue 模板,用类型 int 代替模板形参的每次出现而创建 Queue 类。实例化的类就像已经编写的一样:

     // simulated version of Queue instantiated for type int
     template <class Type> class Queue<int> {
     public:
         Queue();                  // this bound to Queue<int>*
         int &front();             // return type bound to int
         const int &front() const; // return type bound to int
         void push(const int &);   // parameter type bound to int
         void pop();               // type invariant code
         bool empty() const;       // type invariant code
     private:
         // ...
     };

To create a Queue class for objects of type string, we'd write:

要为 string 类型的对象创建 Queue 类,可以编写

     Queue<string> qs;

In this case, each occurrence of Type would be replaced by string.

在这个例子中,用 string 代替 Type 的每次出现。

Each instantiation of a class template constitutes an independent class type. The Queue instantiation for the type int has no relationship to nor any special access to the members of any other Queue type.

类模板的每次实例化都会产生一个独立的类类型。为 int 类型实例化的 Queue 与任意其他 Queue 类型没有关系,对其他 Queue 类型成员也没有特殊的访问权。



Class Template Arguments Are Required

类模板形参是必需的

When we want to use a class template, we must always specify the template arguments explicitly.

想要使用类模板,就必须显式指定模板实参:

     Queue qs; // error: which template instantiation?

A class template does not define a type; only a specific instantiation defines a type. We define a specific instantiation by providing a template argument to match each template parameter. Template arguments are specified in a comma-separated list and bracketed by the (<) and (>) tokens:

类模板不定义类型,只有特定的实例才定义了类型。特定的实例化是通过提供模板实参与每个模板形参匹配而定义的。模板实参在用逗号分隔并用尖括号括住的列表中指定:

     Queue<int> qi;         // ok: defines Queue that holds ints
     Queue<string> qs;      // ok: defines Queue that holds strings

The type defined by a template class always includes the template argument(s). For example, Queue is not a type; Queue<int> or Queue<string> are.

用模板类定义的类型总是模板实参。例如,Queue 不是类型,而 Queue<int>Queue<string> 是类型。

Function-Template Instantiation

函数模板实例化

When we use a function template, the compiler will usually infer the template arguments for us:

使用函数模板时,编译器通常会为我们推断模板实参:

     int main()
     {
        compare(1, 0);             // ok: binds template parameter to int
        compare(3.14, 2.7);        // ok: binds template parameter to double
        return 0;
     }

This program instantiates two versions of compare: one where T is replaced by int and the other where it is replaced by double. The compiler essentially writes for us these two instances of compare:

这个程序实例化了 compare 的两个版本:一个用 int 代替 T,另一个用 double 代替 T,实质上是编译器为我们编写了 compare 的这两个实例:

     int compare(const int &v1, const int &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;
     }

16.2.1. Template Argument Deduction

16.2.1. 模板实参推断

To determine which functions to instantiate, the compiler looks at each argument. If the corresponding parameter was declared with a type that is a type parameter, then the compiler infers the type of the parameter from the type of the argument. In the case of compare, both arguments have the same template type: they were each declared using the type parameter T.

要确定应该实例化哪个函数,编译器会查看每个实参。如果相应形参声明为类型形参的类型,则编译器从实参的类型推断形参的类型。在 compare 的例子中,两个实参有同样的模板类型,都是用类型形参 T 声明的。

In the first call, compare(1, 0), those arguments are type int; in the second, compare(3.14, 2.7), they have type double. The process of determining the types and values of the template arguments from the type of the function arguments is called template argument deduction.

第一个调用 compare(1, 0) 中,实参为 int 类型;第二个调用 compare(3.14, 2.7) 中,实参为 double 类型。从函数实参确定模板实参的类型和值的过程叫做模板实参推断

Multiple Type Parameter Arguments Must Match Exactly
多个类型形参的实参必须完全匹配

A template type parameter may be used as the type of more than one function parameter. In such cases, template type deduction must generate the same template argument type for each corresponding function argument. If the deduced types do not match, then the call is an error:

模板类型形参可以用作一个以上函数形参的类型。在这种情况下,模板类型推断必须为每个对应的函数实参产生相同的模板实参类型。如果推断的类型不匹配,则调用将会出错:

     template <typename T>
     int compare(const T& v1, const T& v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }
     int main()
     {
         short si;
         // error: cannot instantiate compare(short, int)
         // must be: compare(short, short) or
         // compare(int, int)
         compare(si, 1024);
         return 0;
     }

This call is in error because the arguments to compare don't have the same type. The template argument deduced from the first argument is short; the one for the second is int. These types don't match, so template argument deduction fails.

这个调用是错误的,因为调用 compare 时的实参类型不相同,从第一个实参推断出的模板类型是 short,从第二个实参推断出 int 类型,两个类型不匹配,所以模板实参推断失败。

If the designer of compare wants to allow normal conversions on the arguments, then the function must be defined with two type parameters:

如果 compare 的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义:

     // argument types can differ, but must be compatible
     template <typename A, typename B>
     int compare(const A& v1, const B& v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

Now the user may supply arguments of different types:

现在用户可以提供不同类型的实参了:

     short si;
     compare(si, 1024); // ok: instantiates compare(short, int)

However, a < operator must exist that can compare values of those types.

但是,比较那些类型的值的 < 操作符必须存在。

Limited Conversions on Type Parameter Arguments
类型形参的实参的受限转换

Consider the following calls to compare:

考虑下面的 compare 调用:

     short s1, s2;
     int i1, i2;
     compare(i1, i2);           // ok: instantiate compare(int, int)
     compare(s1, s2);           // ok: instantiate compare(short, short)

The first call generates an instance of compare with T bound to int. A new instance is created for the second call, binding T to short.

第一个调用产生将 T 绑定到 int 的实例,为第二个调用创建新实例,将 T 绑定到 short

Had compare(int, int) been an ordinary nontemplate function, then the second call would match that function. The short arguments would be promoted (Section 5.12.2, p. 180) to int. Because compare is a template, a new function is instantiated with the type parameter bound to short.

如果 compare(int, int) 是普通的非模板函数,则第二个调用会匹配那个函数,short 实参将提升(第 5.12.2 节)为 int。因为 compare 是一个模板,所以将实例化一个新函数,将类型形参绑定到 short

In general, arguments are not converted to match an existing instantiation; instead, a new instance is generated. There are only two kinds of conversions that the compiler will perform rather than generating a new instantiation:

一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:

  • const conversions: A function that takes a reference or pointer to a const can be called with a reference or pointer to nonconst object, respectively, without generating a new instantiation. If the function takes a nonreference type, then const is ignored on either the parameter type or the argument. That is, the same instantiation will be used whether we pass a const or nonconst object to a function defined to take a nonreference type.

    const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。

  • array or function to pointer conversions: If the template parameter is not a reference type, then the normal pointer conversion will be applied to arguments of array or function type. An array argument will be treated as a pointer to its first element, and a function argument will be treated as a pointer to the function's type.

    数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。

As examples, consider calls to the functions fobj and fref. The fobj function copies its parameters, whereas fref's parameters are references:

例如,考虑对函数 fobjfref 的调用。fobj 函数复制它的形参,而 fref 的形参是引用:

     template <typename T> T fobj(T, T); // arguments are copied
     template <typename T>
     T fref(const T&, const T&);       // reference arguments
     string s1("a value");
     const string s2("another value");
     fobj(s1, s2);     // ok: calls f(string, string), const is ignored
     fref(s1, s2);     // ok: non const object s1 converted to const reference
     int a[10], b[42];
     fobj(a, b); // ok: calls f(int*, int*)
     fref(a, b); // error: array types don't match; arguments aren't converted to pointers

In the first case, we pass a string and a const string as arguments. Even though these types do not match exactly, both calls are legal. In the call to fobj, the arguments are copied, so whether the original object is const doesn't matter. In the call to fref, the parameter type is a reference to const. Conversion to const for a reference parameter is one of the acceptable conversions, so this call is also okay.

第一种情况下,传递 string 对象和 const string 对象作为实参,即使这些类型不完全匹配,两个调用也都是合法的。在 fobj 的调用中,实参被复制,因此原来的对象是否为 const 无关紧要。在 fref 的调用中,形参类型是 const 引用,对引用形参而言,转换为 const 是可以接受的转换,所以这个调用也正确。

In the next case, we pass array arguments in which the arrays are different sizes. In the call to fobj, the fact that the arrays are different doesn't matter. Both arrays are converted to pointers. The template parameter type in fobj is int*. The call to fref, however, is illegal. When the parameter is a reference (Section 7.2.4, p. 240), the arrays are not converted to pointers. The types of a and b don't match, so the call is in error.

在第二种情况中,将传递不同长度的数组实参。fobj 的调用中,数组不同无关紧要,两个数组都转换为指针,fobj 的模板形参类型是 int*。但是,fref 的调用是非法的,当形参为引用时(第 7.2.4 节),数组不能转换为指针,ab 的类型不匹配,所以调用将出错。

Normal Conversions Apply for Nontemplate Arguments
应用于非模板实参的常规转换

The restriction on type conversions applies only to those arguments whose types are template parameters.

类型转换的限制只适用于类型为模板形参的那些实参。



Normal conversions (Section 7.1.2, p. 229) are allowed for parameters defined using ordinary types. The following function template sum has two parameters:

用普通类型定义的形参可以使用常规转换(第 7.1.2 节),下面的函数模板 sum 有两个形参:

     template <class Type> Type sum(const Type &op1, int op2)
     {
         return op1 + op2;
     }

The first parameter, op1, has a template parameter type. Its actual type cannot be known until the function is used. The type of the second parameter, op2, is known: It's int.

第一个形参 op1 具有模板形参类型,它的实际类型到函数使用时才知道。第二个形参 op2 的类型已知,为 int

Because the type of op2 is fixed, normal conversions can be applied to arguments passed to op2 when sum is called:

因为 op2 的类型是固定的,在调用 sum 的时候,可以对传递给 op2 的实参应用常规转换:

     double d = 3.14;
     string s1("hiya"), s2(" world");
     sum(1024, d); // ok: instantiates sum(int, int), converts d to int
     sum(1.4, d); // ok: instantiates sum(double, int), converts d to int
     sum(s1, s2); // error: s2 cannot be converted to int

In the first two calls, the type of the second argument dd is not the same as the type of the corresponding function parameter. However, these calls are okay: There is a conversion from double to int. Because the type of the second parameter does not depend on a template parameter, the compiler will implicitly convert dd. The first call causes the function sum(int, int) to be instantiated; sum(double, int) is instantiated by the second call.

在前两个调用中,第二个实参 dd 的类型与对应函数形参的类型不同,但是,这些调用是正确的:有从 doubleint 的转换。因为第二个形参的类型独立模板形参,编译器将隐式转换 dd。第一个调用导致实例化函数 sum(int, int),第二个调用实例化 sum(double, int)

The third call is an error. There is no conversion from string to int. Using a string argument to match an int parameter is, as usual, illegal.

第三个调用是错误的。没有从 stringint 的转换,使用 string 实参来匹配 int 形参与一般情况一样,是非法的。

Template Argument Deduction and Function Pointers
模板实参推断与函数指针

We can use a function template to initialize or assign to a function pointer (Section 7.9, p. 276). When we do so, the compiler uses the type of the pointer to instantiate a version of the template with the appropriate template argument(s).

可以使用函数模板对函数指针进行初始化或赋值(第 7.9 节),这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。

As an example, assume we have a function pointer that points to a function returning an int that takes two parameters, each of which is a reference to a const int. We could use that pointer to point to an instantiation of compare:

例如,假定有一个函数指针指向返回 int 值的函数,该函数接受两个形参,都是 const int 引用,可以用该指针指向 compare 的实例化

     template <typename T> int compare(const T&, const T&);
     // pf1 points to the instantiation int compare (const int&, const int&)
     int (*pf1) (const int&, const int&) = compare;

The type of pf1 is "pointer to function returning an int taking two parameters of type const int&." The type of the parameters in pf1 determines the type of the template argument for T. The template argument for T is int. The pointer pf1 refers to the instantiation with T bound to int.

pf1 的类型是一个指针,指向“接受两个 const int& 类型形参并返回 int 值的函数”,形参的类型决定了 T 的模板实参的类型,T 的模板实参为 int 型,指针 pf1 引用的是将 T 绑定到 int 的实例化。

When the address of a function-template instantiation is taken, the context must be such that it allows a unique type or value to be determined for each template parameter.

获取函数模板实例化的地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。



It is an error if the template arguments cannot be determined from the function pointer type. For example, assume we have two functions named func. Each function takes a pointer to function argument. The first version of func takes a pointer to a function that has two const string reference parameters and returns a string. The second version of func takes a pointer to a function taking two const int reference parameters and returning an int. We cannot use compare as an argument to func:

如果不能从函数指针类型确定模板实参,就会出错。例如,假定有两个名为 func 的函数,每个函数接受一个指向函数实参的指针。func 的第一个版本接受有两个 const string 引用形参并返回 string 对象的函数的指针,func 的第二个版本接受带两个 const int 引用形参并返回 int 值的函数的指针,不能使用 compare 作为传给 func 的实参:

     // overloaded versions of func; each take a different function pointer type
     void func(int(*) (const string&, const string&));
     void func(int(*) (const int&, const int&));
     func(compare); // error: which instantiation of compare?

The problem is that by looking at the type of func's parameter, it is not possible to determine a unique type for the template argument. The call to func could instantiate either of the following functions:

问题在于,通过查看 func 的形参类型不可能确定模板实参的唯一类型,对 func 的调用可以实例化下列函数中的任意一个:

     compare(const string&, const string&)
     compare(const int&, const int&)

Because it is not possible to identify a unique instantiation for the argument to func, this call is a compile-time (or link-time) error.

因为不能为传给 func 的实参确定唯一的实例化,该调用会产生一个编译时(或链接时)错误。

Exercises Section 16.2.1

Exercise 16.19:

What is instantiation?

什么是实例化?

Exercise 16.20:

What happens during template argument deduction?

在模板实参推断期间发生什么?

Exercise 16.21:

Name two type conversions allowed on function arguments involved in template argument deduction.

指出对模板实参推断中涉及的函数实参允许的类型转换。

Exercise 16.22:

Given the following templates

对于下面的模板

     template <class Type>
     Type calc (const Type* array, int size);
     template <class Type>
     Type fcn(Type p1,Type p2;

which ones of the following calls, if any, are errors? Why?

下面这些调用有错吗?如果有,哪些是错误的?为什么?

     double dobj;    float fobj;    char cobj;
     int ai[5] = { 511, 16, 8, 63, 34 };

     (a) calc(cobj, 'c');
     (b) calc(dobj, fobj);
     (c) fcn(ai, cobj);


16.2.2. Function-Template Explicit Arguments

16.2.2. 函数模板的显式实参

In some situations, it is not possible to deduce the types of the template arguments. This problem arises most often when a function return type must be a type that differs from any used in the parameter list. In such situations, it is necessary to override the template argument deduction mechanism and explicitly specify the types or values to be used for the template parameters.

在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。在这种情况下,有必要覆盖模板实参推断机制,并显式指定为模板形参所用的类型或值。

Specifying an Explicit Template Argument
指定显式模板实参

Consider the following problem. We wish to define a function template called sum that takes arguments of two differnt types. We'd like the return type to be large enough to contain the sum of two values of any two types passed in any order. How can we do that? How should we specify sum's return type?

考虑下面的问题。我们希望定义名为 sum、接受两个不同类型实参的函数模板,希望返回类型足够大,可以包含按任意次序传递的任意两个类型的两个值的和,怎样才能做到?应如何指定 sum 的返回类型?

     // T or U as the returntype?
     template <class T, class U> ??? sum(T, U);

In this case, the answer is that neither parameter works all the time. Using either parameter is bound to fail at some point:

在这个例子中,答案是没有一个形参在任何时候都可行,使用任一形参都一定会在某些时候失败:

     // neither T nor U works as return type
     sum(3, 4L); // second type is larger; want U sum(T, U)
     sum(3L, 4); // first type is larger; want T sum(T, U)

One approach to solving this problem would be to force callers of sum to cast (Section 5.12.4, p. 183) the smaller type to the type we wish to use as the result:

解决这一问题的一个办法,可能是强制 sum 的调用者将较小的类型强制转换(第 5.12.4 节)为希望作为结果使用的类型:

     // ok: now either T or U works as return type
     int i; short s;
     sum(static_cast<int>(s), i); // ok: instantiates int sum(int, int)

Using a Type Parameter for the Return Type
在返回类型中使用类型形参

An alternative way to specify the return type is to introduce a third template parameter that must be explicitly specified by our caller:

指定返回类型的一种方式是引入第三个模板形参,它必须由调用者显式指定:

     // T1 cannot be deduced: it doesn't appear in the function parameter list
     template <class T1, class T2, class T3>
     T1 sum(T2, T3);

This version adds a template parameter to specify the return type. There is only one catch: There is no argument whose type can be used to infer the type of T1. Instead, the caller must explicitly provide an argument for this parameter on each call to sum.

这个版本增加了一个模板形参以指定返回类型。只有一个问题:没有实参的类型可用于推断 T1 的类型,相反,调用者必须在每次调用 sum 时为该形参显式提供实参。

We supply an explicit template argument to a call in much the same way that we define an instance of a class template. Explicit template arguments are specified in a comma-separated list, bracketed by the less-than (<) and greater-than (>) tokens. The list of explicit template types appears after the function name and before the argument list:

为调用提供显式模板实参与定义类模板的实例很类似,在以逗号分隔、用尖括号括住的列表中指定显式模板实参。显式模板类型的列表出现在函数名之后、实参表之前:

     // ok T1 explicitly specified; T2 and T3 inferred from argument types
     long val3 = sum<long>(i, lng); // ok: calls long sum(int, long)

This call explicitly specifies the type for T1. The compiler deduces the types for T2 and T3 from the arguments passed in the call.

这一调用显式指定 T1 的类型,编译器从调用中传递的实参推断 T2T3 的类型。

Explicit template argument(s) are matched to corresponding template parameter(s) from left to right; the first template argument is matched to the first template parameter, the second argument to the second parameter, and so on. An explicit template argument may be omitted only for the trailing (rightmost) parameters, assuming these can be deduced from the function parameters. If our sum function had been written as

显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推。假如可以从函数形参推断,则结尾(最右边)形参的显式模板实参可以省略。如果这样编写 sum 函数:

     // poor design: Users must explicitly specify all three template parameters
     template <class T1, class T2, class T3>
     T3 alternative_sum(T2, T1);

then we would always have to specify arguments for all three parameters:

则总是必须为所有三个形参指定实参:

     // error: can't infer initial template parameters
     long val3 = alternative_sum<long>(i, lng);
     // ok: All three parameters explicitly specified
     long val2 = alternative_sum<long, int, long>(i, lng);

Explicit Arguments and Pointers to Function Templates
显式实参与函数模板的指针

Another example where explicit template arguments would be useful is the ambiguous program from page 641. We could disambiguate that case by using explicit template argument:

可以使用显式模板实参的另一个例子是第 16.2.1 节中有二义性程序,通过使用显式模板实参能够消除二义性:

     template <typename T> int compare(const T&, const T&);
     // overloaded versions of func; each take a different function pointer type
     void func(int(*) (const string&, const string&));
     void func(int(*) (const int&, const int&));
     func(compare<int>); // ok: explicitly specify which version of compare

As before, we want to pass an instantiation of compare in the call to the overloaded function named func. It is not possible to select which instantiation of compare to pass by looking at the parameter lists for the different versions of func. Two different instantiations of compare could satisfy the call. The explicit template argument indicates which instantiation of compare should be used and which func function is called.

像前面一样,需要在调用中传递 compare 实例给名为 func 的重载函数。只查看不同版本 func 的形参表来选择传递 compare 的哪个实例是不可能的,两个不同的实例都可能满足该调用。显式模板形参需要指出应使用哪个 compare 实例以及调用哪个 func 函数。

Exercises Section 16.2.2

Exercise 16.23:

The library max function takes a single type parameter. Could you call max passing it an int and a double? If so, how? If not, why not?

标准库函数 max 接受单个类型形参,可以传递 intdouble 对象调用 max 吗?如果可以,怎样做?如果不能,为什么?

Exercise 16.24:

In Section 16.2.1 (p. 638) we saw that the arguments to the version of compare that has a single template type parameter must match exactly. If we wanted to call the function with compatible types, such as int and short, we could use an explicit template argument to specify either int or short as the parameter type. Write a program that uses the version of compare that has one template parameter. Call compare using an explicit template argument that will let you pass arguments of type int and short.

第 16.2.1 节我们看到,对于具有单个模板类型形参的 compare 版本,传给它的实参必须完全匹配,如果想要用兼容类型如 intshort 调用该函数,可以使用显式模板实参指定 intshort 作为形参类型。编写程序使用具有一个模板形参的 compare 版本,使用允许你传递 intshort 类型实参的显式模板实参调用 compare

Exercise 16.25:

Use an explicit template argument to make it sensible to call compare passing two string literals.

使用显式模板实参,传递两个字符串字面值调用 compare 是切合实际的。

Exercise 16.26:

Given the following template definition for sum

对于下面的 sum 模板定义:

     template <class T1, class T2, class T3> T1 sum(T2, T3);

explain each of the following calls. Indicate which, if any, are errors. For each error, explain what is wrong.

解释下面的每个调用,是否有错?如果有,指出哪些是错误的,对每个错误,解释错在哪里。

     double dobj1, dobj2; float fobj1, fobj2; char cobj1, cobj2;

     (a) sum(dobj1, dobj2);
     (b) sum<double, double, double>(fobj1, fobj2);
     (c) sum<int>(cobj1, cobj2);
     (d) sum<double, ,double>(fobj2, dobj2);


Team LiB
Previous Section Next Section