您的位置:首页 > 编程语言 > C语言/C++

《C++ primer》学习笔记之二十五:template function 的实例化

2006-04-14 01:35 274 查看
2.何时实例化:
(1).遇到一个函数调用或取函数地址,如果在普通函数定义中找到了匹配的定义,那么直接用普通的函数
如果遇到显式实例化,那么直接跳到第(3)步
比如:
//te.cpp
void f(int i){ cout << "f(" << i << ")" << endl; }
f(2); //这个调用不会引起实例化template_function

//必须是精确匹配,不需要隐含的转换。下面这个不是精确匹配
f(2.0f); //实例化:void f(float) .

此时就不要实例化了。

(2).如果在普通函数中没有找到精确匹配的,则到显式规格化函数explicit_specialization_template_function中匹配
//声明一个f的显式规格化函数:
//te.h
template <typename T> void f(T t); //声明template_function f
template <> void f<float>(float x); //声明一个f的显式规格化函数。这个必须在f的声明或定义之后
template <> void f(float x); //这样显示规格化也可以(没有<float>了)。因为参数float x已表明template parameter为float
//类推,如果从函数参数中可以推导出template parameter的值,则可以靠后的连续的几个template parameter可以省略,让编译器自己去猜

template <typename T> //定义template_function f
void f(T t){ cout << t << endl; };

//te.cpp
void f(int i){ cout << "f(" << i << ")" << endl; } //普通的函数
template <> void f(float x) //explicit_specializztion_template_function定义
{ cout << "f(" << x << "f)" << endl; }

f(2); //输出:f(2) 这个是普通函数
f(2.0f); //输出:f(2f) 这个即是显式规格化的那位
f(2.0); //输出:double 这个是template_function实例化void f(double)后的输出

与普通函数一样:显式规格化template函数要先在header中声明,然后再在.cpp中定义,否则出现“重复定义”的编译错
不过很奇怪的是,可以在同一个.cpp中定义多次显式规格化template函数,且body各异。不过结果是以第一次的为准。
但不可以在不同的.cpp中定义同一个显示规格化template函数,否则出现“重复定义”的编译错
与普通函数一样:如果显示规格化template函数只声明却没有定义,则会报“定义找不到”的link错

此时的匹配与1中一样,也是精确匹配
(3).如果2也没有找到匹配的函数,则到template_function中匹配
匹配时,找符合调用函数参数的template_function,并用匹配的类型去实例化template_function。怎么匹配,请看3如何实例化
该实例化是隐式实例化
//te.cpp
class ClassA{}
template <class T>
class TemClassB{}

int main()
{
f(2.0); //输出:f(double) ——类似的还有f(2L):f(long), f(2LU):f(unsigned long)
f('a'); //f(char)
f("a"); //f(char const *) ——注意这里有个const修饰,所以在f中试图改变指针的内容会报编译错。
char *p = "a";
f(p); //f(char *)
char *&rp = p;
f(rp); //f(char *) ——注意引用符号并没有输出来,但引用符号确实可以作为重载的材料

ClassA a;
f(a); //f(class ClassA) ——类似的还有f(union un), f(enum en)

TemClassB<int> b;
f(b); //f(class TemClassB<int>)

string str = "a";
f(str); //f(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)

TemClassB<string> bs;
f(bs); //f(class B<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >)

class LocalClass{};
LocalClass c;
f(c); //error.在模板实例化中使用局部类型

return 0;
}

3. 如何实例化
实质就是编译器决定用什么template_parameter去实例化模板函数。
实际上在第2步中有个匹配的过程,"匹配"即是决定模板参数的过程。
(1) 每个调用函数使用的参数将与函数定义的参数一一对应,但不对应返回值。
显然,参数个数要一样。template_function的template_parameter不能有default值,所以不存在省略某个的参数的做法
template <class T> void f(T t1, T t2) {}
f("a", "b"); //调用参数"a"对应T t1, 调用参数"b"对应T t2

template <class T, class S> S f(T t) { return S(); }
f("a"); //调用参数"a"对应T t
int (*pf)(float) = f; //调用参数类型float对应T,返回值类型int不对应S

(2) 如果定义中,参数的类型为已知类型,即不含template_parameter中的类型。那么调用的参数可以作ordinary_conversion常规转换到要求的类型
如果定义中,参数的类型为parameter type,那么如果调用的参数作下面的转换可以到要求的类型,则也算匹配:
1. lvalue -> rvalue:
parameter type为rvalue,而调用的参数为lvalue
比如:
template <class T> void f(T t) {}
template <class S> void g(S &s) {}
int main()
{
f(0); //这里就不需要lvalue-rvalue的转换,因为0为rvalue, t也为rvalue

int i = 0;
f( i ); //这里就需要lvalue-rvalue的转换,因为i为rvalue, t为rvalue

g(0); //error:int不能转换为int&. 这里企图进行rvalue-lvalue的转换,但是不容许
g(i); //i为rvalue, s为rvalue,不需要任何转换
}
一般,如果函数的参数和返回值不是一个引用类型,那么它返回的就是一个rvalue的转换。
2. qualification conversion:
这里的qualification指const, volatile.
parameter type中含这些qualification,但调用的参数中则没有
比如:
template <class T> void f(T const t) {}
int main()
{
int i = 0;
f( i ); //t为int const类型,而i为int类型。qualification conversion发生
}
3. derived-base
parameter type为基类,而调用的参数为子类

注意:当参数类型为数组类型时,其实质为指针。所以数组——指针不算转换。

这三种转换为exactly conversion.
当然,如果可能,编译器不做任何转换即得出parameter type的值。
经过本匹配,编译器可以得到一个parameter type的值
(3)依次顺序地分析所有调用的参数。
如果分析中发现某个parameter type有两种类型,编译器是不会试着去转换这两者类型的,直接pass掉,即认为调用与本目标不匹配。
template <class T> void f(T t1, T t2) {}
f('1', 2); //error:模板参数T不明确
//分析参数'1'时得出T为char, 分析参数2时得出T为int,两者矛盾

如果分析完后,parameter list中仍然有人没有值,那么匹配不成功。
template <class T, class S> S f(T t) { return S(); }
int i = f(2); //error:未能推导出S的模板参数
//T为int
//虽然调用时要求返回值为int类型,但编译器是靠函数调用的参数列表来分析出模板参数的。此时分析不出来S的类型来。

int (*pf)(int) = f; //error:未能推导出S的模板参数。原因与上面相同

(4)按(1)(2)(3)步骤匹配其它重载的函数,找到"最匹配的函数"。
如果有两个“最匹配的函数”,则报“ambiguous call to overloaded function”编译错,即“两义调用”
比如:
template <class T> void f(T t1, T t2) {} //1
template <class T> void f(T t1, T const t2) {} //error. const不能作为重载的材料
template <class T> void f(T t1, double d) {} //3
template <class T> void f(char c, T t2) {} //4

template <class T> void g(T t1, double d) {} //5

int main()
{
f(1, 3); //1匹配, T 为 int,
f(1, 3.0); //3匹配, T 为 int
f('1', 3); //4匹配, T 为 int
f('1', 3.0); //error: 3和4都为最佳匹配,则为两义调用。

g(1, 3); //5匹配, T 为int。 参数3会转换为类型double

}

例外:
template <class T> void f(T t1, T t2) {}
template <class T, class S> void f(T t, S s) {}
f(3, 4) //调用 f(T t1, T t2) 还是 f(T t, S s)?好像都是最佳匹配?是否有编译错?
//ok. 调用f(T t1, T t2)。
//与两函数定义的次序没有关系。具体原因未知。按理f(T t1, T t2)更匹配是合理的,难道是"最佳最简单匹配"?

不过:
template <class T, class S> S f(T t1, T t2) {} //与上面的区别是返回值为S类型
template <class T, class S> S f(T t, S s) {} //与上面的区别是返回值为S类型
f(3, 4); //ok.调用f(T t, S s)。
//因为f(T t1, T t2)函数推导不出S的值,所以pass掉。此处是与上面的例外作比较。

注意:如果让编译器自己推导template_parameter的值不太直观的话,可以显示调用,即直接指定参数的值:
f<int, int>(3, 4);
f<int, double>(3, 4); //这个调用参数4会转换为double型
f<int, string>(3, 4); //error:4无法转换为std::string类型

如果靠后的参数编译器自己可以推导出来,那么可以省略:
f<int>(3, 4);
f<int>(3, 4.0); //这个与f<int, double>(3, 4.0)等

上面的输出中显示了匹配的函数规格,这个也就是用来作为实例化模板函数的template_parameter

如果第3步也没有找到匹配的函数,则该调用就错了。

一旦在第(2)、(3)步中引起了实例化,那么实例化生成的函数就成为了一个普通的函数,那么它就会碰到些普通函数会遇到的问题:
1.如果在实例化地方的后面又有相同原形的函数定义,则报“重复定义”
比如在两个.cpp中都显式规格化同一个template_function

比如在实例化地方的后面又有一个普通函数的定义或显示规格化template_function的定义有相同原形

在header中声明所有函数,或在调用前声明所有函数,可避免这种错误。

2.实例化后所有template parameter都确定了,所以在函数体中使用这些parameter的地方是否符合语法,编译器会检查
比如:
template <class T>
void f(T t) { cout << T << T.size() << endl; }
函数f假定类型T支持'<<'操作符,并且有size()成员函数,如果实例化后发现没有,则会报编译错。
这种错误正是显式规格化模板函数的最大用处。对于例外的且常见的类型显式定义一个规格可以解决本问题。
3. 同一个文件中,如果再碰到匹配的函数调用,则编译器不会再实例化了。
但在不同的文件中,会各自实例化同一个函数规格,好像它们不去判断其他人是否已实例化同一个函数规格。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: