第十六章

16.1.1

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

16.1.3

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

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

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

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

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

// 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) { /* ... */ }

        编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。

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

        如果拿不准是否需要以 typename 指明一个名字是一个类型,那么指定它是个好主意。在类型之前指定 typename 没有害处,因此,即使 typename 是不必要的,也没有关系。(typename有两个地方不可用,一是base class list,二是member initialization list,详见条款42)

16.1.6

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

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

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

        编写模板代码时,对实参类型的要求尽可能少是很有益的。它说明了编写泛型代码的两个重要原则 

  • 模板的形参是 const 引用。将形参设为 const 引用,就可以允许使用不允许复制的类型。 
  • 函数体中的测试只用 < 比较。可以减少对可用于 compare 函数的类型的要求,这些类型必须支持 <,但不必支持 >。
16.2.1
多个类型形参的实参必须完全匹配
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 的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义:

// 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 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 <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 ignored798
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 是可以接受的转换,所以这个调用也正确。

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

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

template <class Type> 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
模板实参推断与函数指针 
        使用函数指针进行函数模板实例化:
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;

        使用函数指针进行函数模板实例化的时候必须保证:它允许为每个模板形参确定唯一的类型或值(唯一性)

// 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&)
16.2.2
在返回类型中使用类型形参 

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

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

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

// poor design: Users must explicitly specify all three template parameters
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);

        显示模板实参可以使用类型转换

template<typename T> int compare(const T &v1,const T &v2);
short sval=123;
int ival=1024;
compare<int>(sval,ival);
compare<short>(sval,ival);
显式实参与函数模板的指针

        通过使用显式模板实参能够消除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

16.3

        一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中, 而普通函数和类成员函数的定义放在源文件中。 

        模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要那些通常放在源文件中的代码。

        标准 C++ 为编译模板代码定义了两种模型。在两种模型中,构造程序的方式很大程度上是相同的:类定义和函数声明放在头文件中,而函数定义和成员定义放在源文件中。两种模型的不同在于,编译器怎样使用来自源文件的定义

包含编译

        编译器都支持第一种模型:可以通过在声明函数模板或类模板的头文件中添加一条 #include 指示使定义可用, 该#include 引入了包含相关定义的源文件。

分别编译
         在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,可以使用 export 关键字来做这件事。
// out.h:(声明头文件——只包含out函数的声明信息)
template<class T> void out (const T& t);

// out.cpp:(定义代码文件——包含out函数的声明[通过include]和定义等全部信息)
#include <iostream>
#include “out.h”
export template<class T> void out (const T& t) {...}

//user.cpp:(用户代码文件——包含函数的声明头文件后就可以使用该函数)
#include “out.h”

// 使用out()

        对类模板使用 export 更复杂一些。通常,类声明必须放在头文件中,头文件中的类定义体不应该使用关键字 export,如果在头文件中使用了 export,则该头文件只能被程序中的一个源文件使用导出类的成员将自动声明为导出的。也可以将类模板的个别成员声明为导出的(点击打开链接

16.4

         通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名例如,在默认构造函数和复制构造函数的声明中,名字 Queue 是 Queue<Type> 缩写表示。实质上,编译器推断,当我们引用类的名字时,引用的是同一版本。

类模板成员函数的实例化

        编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定。这意味着调用类模板成员函数比调用类似函数模板更灵活。用模板形参定义的函数形参的实参允许进行常规转换(没有自定义类类型,条款46中有一例子)

Queue<int> qi; // instantiates class Queue<int>
short s = 42;
int i = 42;
// ok: s converted to int and passed to push
qi.push(s); // instantiates Queue<int>::push(const int&)
qi.push(i); // uses Queue<int>::push(const int&)
f(s); // instantiates f(const short&)817
f(i); // instantiates f(const int&)
何时实例化类和成员

        类模板的成员函数只有为程序所用才进行实例化。如果某函数从未使用,则不会实例化该成员函数

        类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。因此,在创建 Queue 对象不会实例化 QueueItem 类,相反,在使用诸如 front、push 或 pop 这样的Queue 成员时才实例化 QueueItem 类。

16.4.2. 非类型形参的模板实参

        非类型模板实参必须是编译时常量表达式 

16.4.3

        编译器将友元声明也当作类或函数的声明对待。想要限制对特定实例化的友元关系时, 必须在可以用于友元声明之前声明类或函数: 

template <class T> class A;
template <class T> class B {
public:
friend class A<T>;         // ok: A is known to be a template
friend class C;            // ok: C must be an ordinary,
nontemplate class
template <class S> friend class D; // ok: D is a template
friend class E<T>;         // error: E wasn't declared as a template
friend class F<int>;      // error: F wasn't declared as a template
};
16.4.5 成员模板

        任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚

template <class Type> class Queue {
public:
// construct a Queue from a pair of iterators on some sequence
template <class It>
Queue(It beg, It end):head(0), tail(0) { copy_elems(beg, end); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// rest of Queue class as before
private:
// version of copy to be used by assign to copy elements from iterator range
template <class Iter> void copy_elems(Iter, Iter);
};
template <class T> template <class Iter> void Queue<T>::assign(Iter beg, Iter end)
{
    destroy(); // remove existing elements in this Queue
    copy_elems(beg, end); // copy elements from the input range
}

        当成员模板是类模板的成员时,它的定义必须包含类模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表

成员模板实例化

        成员模板只有在程序中使用时才实例化成员模板有两种模板形参:由类定义的和由成员模板本身定义的。类模板形参由调用函数的对象的类型确定,成员定义的模板形参的行为与普通函数模板一样,这些形参都通过常规模板实参推断而确定

short a[4] = { 0, 3, 6, 9 };
// instantiates Queue<int>::Queue(short *, short *)
Queue<int> qi(a, a + 4); // copies elements from a into qi
vector<int> vi(a, a + 4);
// instantiates Queue<int>::assign(vector<int>::iterator,vector<int>::iterator)
qi.assign(vi.begin(), vi.end());

        因为所构造的是 Queue<int> 类型的对象,我们知道编译器将为Queue<int> 实例化基于迭代器的构造函数。该构造函数本身模板形参的类型由编译器根据 a 和 a+4 的类型推断,而该类型为 short 指针。因此,qi 的定义将实例化 。

        对 assign 的调用将实例化 qi 的成员。qi 具有 Queue<int> 类型,因此,这个调用将实例化名为 assign 的 Queue<int> 成员。该函数本身是函数模板,像对任意其他函数模板一样,编译器从传给调用的实参推断 assign 的模板实参推断得到的类型是 vector<int>::iterator,即,这个调用将实例化 void Queue<int>::assign(vector<int>::iterator,vector<int>::iterator)。

16.4.7 类模板的static成员
template <class T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};

        每个实例化表示截然不同的类型,所以给定实例所有对象都共享一个static 成员。因此,Foo<int> 类型的任意对象共享同一 static 成员 ctr,Foo<string> 类型的对象共享另一个不同的 ctr 成员

        与任意其他成员函数一样,static 成员函数只有在程序中使用时才进行实例化。

        在类模板含有 static 成员的情况下, 成员定义必须指出它是类模板的成员:

template <class T> size_t Foo<T>::ctr = 0; // define and initialize ctr
16.6.1 函数模板特化
  • 关键字 template 后面接一对空的尖括号(<>);
  • 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;
  • 函数形参表;
  • 函数体。

        如果可以从函数形参表推断模板实参,则不必显式指定模板实参:

// ok: explicit template argument const char* deduced from parameter types
template<> int compare(const char* const&,const char* const&);

        当定义非模板函数的时候,对实参应用常规转换;当特化模板的时候,对实参类型不应用转换。在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配如果不完全匹配,编译器将为实参从模板定义实例化一个实例

16.6.2 类特化定义
template<> class Queue<const char*> {...};

        在类特化外部定义成员时,成员之前不能加 template<> 标记 :

void Queue<const char*>::push(const char* val)
{
return real_queue.push(val);
}
16.6.3 特化成员而不是类
template <> void Queue<const char*>::push(const char *const &val);
16.6.4 类模板的部分特化

        如果类模板有一个以上的模板形参,我们也许想要特化某些模板形参而非全部。使用类模板的部分特化可以做到这一点:

template <class T1, class T2>
class some_template {
// ...
};
// partial specialization: fixes T2 as int and allows T1 to vary
template <class T1> class some_template<T1, int> {
// ...
};

        类模板的部分特化本身也是模板,像任何其他类模板一样,部分特化是在程序中使用时隐式实例化: 

some_template<int, string> foo; // uses template
some_template<string, int> bar; // uses partial specialization

        部分特化的定义与通用模板的定义完全不会冲突。部分特化可以具有与通用类模板完全不同的成员集合。类模板成员的通用定义永远不会用来实例化类模板部分特化的成员

16.7 重载与函数模板

        函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数

如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下:

1. 为这个函数名建立候选函数集合,包括:
    a. 与被调用函数名字相同的任意普通函数

    b. 任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参

2. 确定哪些普通函数是可行的(第 7.8.2 节)(如果有可行函数的话)。候选集合中的每个模板实例都可行的,因为模板实参推断保证函数可以被调用

3. 如果需要转换来进行调用,根据转换的种类排列可靠函数,记住,调用模板函数实例所允许的转换是有限的。
    a. 如果只有一个函数可选,就调用这个函数

    b. 如果调用有二义性,从可行函数集合中去掉所有函数模板实例

4. 重新排列去掉函数模板实例的可行函数。
    a. 如果只有一个函数可选,就调用这个函数

    b. 否则,调用有二义性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值