C++ 之 模板与泛型编程(二、模板实例化)

 模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程称为实例化。

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

 

类的实例化

模板的定义:


     template 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:
         // ...
     };

当定义模板类对象时,

 

     Queue qi;

 

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

// simulated version of Queue instantiated for type int
     class Queue {
     public:
         Queue();                  // this bound to Queue*
         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:
         // ...
     };

 

Queue qi;
Queue          qi; //error

 

用模板类定义的类型总是模板实参。例如,Queue 不是类型,而 QueueQueue 是类型

 

函数模板实例化

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

int main()
     {
        compare(1, 0);             // ok: binds template parameter to int
        compare(3.14, 2.7);        // ok: binds template parameter to double
        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;
     }
模板实参推断

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

 

1、多个类型形参的实参必须完全匹配

     template 
     int compare(const T& v1, const T& v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }
     int main()
     {
         short si;
         
         
         compare(si, 1024);
         return 0;
     }

 

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

 

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

     // argument types can differ, but must be compatible
     template 
     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)

2、类型形参的实参的受限转换

    考虑下面的 compare 调用:

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

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

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

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

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

 

 template T fobj(T, T); // arguments are copied
     template
     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

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

 

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

 

3、应用于非模板实参的常规转换

用普通类型定义的形参可以使用常规转换

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

因为 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

 

4、模板实参推断与函数指针

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

 

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

     template  int compare(const T&, const T&);
     // pf1 points to the instantiation int compare (const int&, const int&)
     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?

 

函数模板的显式实参

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

 

指定显式模板实参

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

 

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

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

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

 

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

在返回类型中使用类型形参

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

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

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

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

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

 

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

 

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

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

 

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

显式实参与函数模板的指针

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

 

template 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&));

 

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


     func(compare); // ok: explicitly specify which version of compare

 

 

 

 

 

你可能感兴趣的:(C/C++)