模板和泛型编程 --实例化-- C++ primer 第十六章(2)

来源:互联网 发布:西门子模拟仿真软件 编辑:程序博客网 时间:2024/05/16 08:22

16.2. 实例化
模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程称为实例化,这个术语反映了创建模板类型或模板函数的新“实例”的概念。
模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。
类的实例化
当编写

     Queue<int> qi;

编译器自动创建名为 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:         // ...     };

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

     Queue<string> qs;

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

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

类模板形参是必需的想要使用类模板,就必须显式指定模板实参:
Queue qs; // error: which template instantiation?

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

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

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

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

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

这个程序实例化了 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. 模板实参推断
要确定应该实例化哪个函数,编译器会查看每个实参。如果相应形参声明为类型形参的类型,则编译器从实参的类型推断形参的类型。在 compare 的例子中,两个实参有同样的模板类型,都是用类型形参 T 声明的。第一个调用 compare(1, 0) 中,实参为 int 类型;第二个调用compare(3.14, 2.7) 中,实参为 double 类型。从函数实参确定模板实参的类型和值的过程叫做模板实参推断。

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

   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;     }

这个调用是错误的,因为调用 compare 时的实参类型不相同,从第一个实参推断出的模板类型是 short,从第二个实参推断出 int 类型,两个类型不匹配,所以模板实参推断失败。
如果 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;     }

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

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

但是,比较那些类型的值的 < 操作符必须存在。
类型形参的实参的受限转换
考虑下面的 compare 调用:
short s1, s2;
int i1, i2;
compare(i1, i2); // ok: instantiate compare(int, int)
compare(s1, s2); // ok: instantiate compare(short,
short)

第一个调用产生将 T 绑定到 int 的实例,为第二个调用创建新实例,将 T绑定到 short。
如果 compare(int, int) 是普通的非模板函数,则第二个调用会匹配那个函数,short 实参将提升(第 5.12.2 节)为 int。因为 compare 是一个模板,所以将实例化一个新函数,将类型形参绑定到 short。
一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:
• const 转换: 接受 const 引用或 const 指针的函数可以分别用非 const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。• 数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数
类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
例如, 考虑对函数 fobj 和 fref 的调用。 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 constreference     int a[10], b[42];     fobj(a, b); // ok: calls f(int*, int*)     fref(a, b); // error: array types don't match; arguments aren'tconverted to pointers

第一种情况下,传递 string 对象和 const string 对象作为实参,即使这些类型不完全匹配,两个调用也都是合法的。在 fobj 的调用中,实参被复制,因此原来的对象是否为 const 无关紧要。 在 fref 的调用中, 形参类型是 const
引用,对引用形参而言,转换为 const 是可以接受的转换,所以这个调用也正确。
在第二种情况中,将传递不同长度的数组实参。fobj 的调用中,数组不同无关紧要,两个数组都转换为指针,fobj 的模板形参类型是 int*。但是,fref的调用是非法的,当形参为引用时(第 7.2.4 节),数组不能转换为指针,a 和
b 的类型不匹配,所以调用将出错。
应用于非模板实参的常规转换

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

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

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

第一个形参 op1 具有模板形参类型,它的实际类型到函数使用时才知道。第二个形参 op2 的类型已知,为 int。
因为 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

在前两个调用中, 第二个实参 dd 的类型与对应函数形参的类型不同, 但是,这些调用是正确的:有从 double 到 int 的转换。因为第二个形参的类型独立模板形参,编译器将隐式转换 dd。第一个调用导致实例化函数 sum(int, int),
第二个调用实例化 sum(double, int)。
第三个调用是错误的。没有从 string 到 int 的转换,使用 string 实参来匹配 int 形参与一般情况一样,是非法的。模板实参推断与函数指针可以使用函数模板对函数指针进行初始化或赋值(第 7.9 节),这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。
例如,假定有一个函数指针指向返回 int 值的函数,该函数接受两个形参,都是 const int 引用,可以用该指针指向 compare 的实例化

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

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

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

如果不能从函数指针类型确定模板实参,就会出错。例如,假定有两个名为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?

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

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

Exercises Section 16.2.1
Exercise
16.19:
什么是实例化?
Exercise
16.20:
在模板实参推断期间发生什么?
Exercise
16.21:
指出对模板实参推断中涉及的函数实参允许的类型转换。
Exercise
16.22:
对于下面的模板

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

下面这些调用有错吗?如果有,哪些是错误的?为什么?
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. 函数模板的显式实参
在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。在这种情况下,有必要覆盖模板实参推断机制,并显式指定为模板形参所用的类型或值。

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

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

在这个例子中,答案是没有一个形参在任何时候都可行,使用任一形参都一定会在某些时候失败:
// 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)

解决这一问题的一个办法,可能是强制 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)

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

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

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

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

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

这一调用显式指定 T1 的类型,编译器从调用中传递的实参推断 T2 和 T3的类型。显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配, 第二个实参与第二个形参匹配, 以此类推。 假如可以从函数形参推断,则结尾(最右边)形参的显式模板实参可以省略。如果这样编写 sum 函数:

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

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

     // 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);

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

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

像前面一样,需要在调用中传递 compare 实例给名为 func 的重载函数。只查看不同版本 func 的形参表来选择传递 compare 的哪个实例是不可能的,两个不同的实例都可能满足该调用。 显式模板形参需要指出应使用哪个 compare
实例以及调用哪个 func 函数。
Exercises Section 16.2.2
Exercise
16.23:
标准库函数 max 接受单个类型形参,可以传递 int 和double 对象调用 max 吗?如果可以,怎样做?如果不能,为什么?
Exercise
16.24:
在第 16.2.1 节我们看到,对于具有单个模板类型形参的 compare 版本,传给它的实参必须完全匹配,如果想要用兼容类型如 int 和 short 调用该函数,可以使用显式模板实参指定 int 或 short 作为形参类型。编写程序使用具有一个模板形参的 compare 版本, 使用允许你传递 int 和 short 类型实参的显式模板实参调用compare。
Exercise
16.25:
使用显式模板实参,传递两个字符串字面值调用compare 是切合实际的。
Exercise
16.26:
对于下面的 sum 模板定义:

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

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

     double dobj1, dobj2; float fobj1, fobj2; charcobj1, cobj2;     (a) sum(dobj1, dobj2);     (b) sum<double, double, double>(fobj1,fobj2);     (c) sum<int>(cobj1, cobj2);     (d) sum<double, ,double>(fobj2, dobj2);
0 0
原创粉丝点击