一、函数模板(Function Template)

本系列是《C++Template》(作者:David Vandevoorde, Nicolai M. Josuttis)的学习总结。

1:函数模板(Function Template)

所谓函数模板是由参数表示的一系列的函数。函数模板可以被不同的类型参数所调用,使用时和普通的函数功能一样,不同的是函数模板在定义的时候参数的类型是未知的。

1.1.、例子,模板定义

template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    // if a < b then use b else use a 
    return a<b?b:a; 
}

以上代码的功能是求两个值中的最大值,参数的类型是不确定的,在此声明为模板参数T。
模板参数的声明如下:

template < comma-separated-list-of-parameters > 

上面的例子中的参数是,由一对<>扩起来。typename 是关键字,T是类型参数标识,如果有多个参数标识符用”,”分隔。当然参数标识符也可以使用其它的标识符,只是习惯上用T(T是Type的首字母)。

注:有的地方关键字typename也会用class替代两者没有差别,但是在本系列学习笔记中用typename,关键字class只用来表示类。

1.2、模板的使用

#include <iostream> 
#include <string> 
#include "max.hpp" 

int main() 
{ 
    int i = 42; 
    std::cout << "max(7,i): " << ::max(7,i) << std::endl; 

    double f1 = 3.4; 
    double f2 = -6.7; 
    std::cout << "max(f1,f2): " << ::max(f1,f2) << std::endl; 

    std::string s1 = "mathematics"; 
    std::string s2 = "math"; 
    std::cout << "max(s1,s2): " << ::max(s1,s2) << std::endl; 
} 

结果如下:

max(7,i):   42 
max(f1,f2): 3.4 
max(s1,s2): mathematics

上面的例子中函数模板max()被调用了三次,每次调用的时候使用的参数类型是int,double和string

注:上面调用max()前面都使用了::,这是因为在标准模板库中也有个std::max()的模板函数,使用::可以确保max()使用的是我们上面定义的max()函数。

上面的过程看起来好像是由一个max()函数处理三个不同类型的参数,但是实际上并不是这样的。我们上面的函数模板max(),被编译器根据调用时的不同类型产生了三个函数体。

使用int的函数体:

inline int const& max (int const& a, int const& b) 
{ 
    // if a < b then use b else use a 
    return a<b?b:a; 
}

其它两种类型的函数体:

const double& max (double const&, double const&); 
const std::string& max (std::string const&, std::string const&); 

由具体的类型取代模板参数的工程被称为实例化。实例化的结果就是产生了执行是调用的真正的函数实例。

注:只要函数模板被调用就会实例化

试图使用某个类型实例化一个模板函数时,如果该类型不支持模板中的操作,就会产生编译错误,例如:

std::complex<float> c1, c2;    // doesn't provide operator < 
… 
max(c1,c2);                    // ERROR at compile time 

上面的例子中,由于比较运算符“<”不支持复数,因此发生编译错误。

在我们编译程序时,实际过程模板会被编译两次。
- 首先,对模板代码检查语法(如缺少分号)等语法错误。
- 然后,实例化时,即调用模板函数时检查模板函数中的操作是否支持该类型。


2、参数推导(Argument Deduction)

在上面的例子中,当调用max()时模板参数是根据调用时的参数推导出来的。如上面传入的是int类型,C++编译器就会推导出T必须是int。自动推导是没有类型转换的,因此传入入的类型必须严格匹配。

template <typename T> 
inline T const& max (T const& a, T const& b); 
… 
max(4,7)     // OK: T is int for both arguments 
max(4,4.2)   // ERROR: first T is int, second T is double

对于第二个max()的调用,由于第一个参数是int,第二个double。因此编译时候会产生错误。
这种错误,有三种方法可以及解决:

1、转换参数,让两个参数严格匹配

max(static_cast<double>(4),4.2)    // OK 

2、显示指定类型T

max<double>(4,4.2)                 // OK 

3、定义函数模板时指定两个不同类型的参数

template <typename T1,typename T2> 
inline T const& max (T2 const& a, T2 const& b) 

3、模板参数(Template Parameters)

函数模板有两种类型的参数:

1、模板参数,使用尖括号”<>”声明在函数模板名之前。

template <typename T>            // T is template parameter 

2、调用参数,使用圆括号”()”声明在函数模板名之后

max (T const& a, T const& b)   // a and b are call parameters 

模板参数的个数可是任意个,但是不能为模板参数指定默认值。

例如,可以为函数模板max()指定两个不同类型:

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 
… 
max(4,4.2)   // OK, but type of first argument defines return type 

上面的例子看起来很好,但是有问题。首先,返回类型必须被声明。如果返回类型是其中的一个模板参数类型,另一个参数类型就可能被转换成返回的类型。另外一个问题,把第二个类型转换为第一个类型会产生局部临时对象,那么就不能使用引用的方式(by reference)传回结果。因此上面的例子中,返回的类型是T1 而不是 T const&

由于调用参数(call parameters )是由模板参数(template parameters)构造的,所以两者是相关的。我们把这种概念称为:函数模板参数推导。有了推导,就可以像调用普通函数那样调用函数模板。
如之前的例子,调用时为函数模板显示指定类型

template <typename T> 
inline T const& max (T const& a, T const& b); 
… 
max<double>(4,4.2)    // instantiate T as double 

当模板参数和调用参数没有直接关系,且编译器也无法推导出模板参数是,就需要明确的指定模板参数了。如,可以为max()指定第三个模板参数类型作为返回参数类型。

template <typename T1, typename T2, typename RT> 
inline RT max (T1 const& a, T2 const& b);

然而,推导机制并不会对返回类型进行匹配,而且模板参数RT也不在调用参数中。因此,编译器无法推导出RT,调用时就必须显示指定类型,如下:

template <typename T1, typename T2, typename RT> 
inline RT max (T1 const& a, T2 const& b); 
… 
max<int,double,double>(4,4.2)    // OK, but tedious 

上面的例子,调用时要么不需要指定参数完全由编译器推导,要么就要把所有的参数多显示指定了。
当然,还有一种方法只明确的指定第一个模板参数,其它的参数由编译器自动推导。
如下:RT要放在第一个参数的位置。

template <typename RT, typename T1, typename T2> 
inline RT max (T1 const& a, T2 const& b); 
… 
max<double>(4,4.2)    // OK: return type is double 

4、函数模板重载(Overloading Function Templates)

和普通的函数一样,函数模板也是可以重载的。
函数重载:不同的函数的定义可以有相同的函数名,当函数被调用的时候由编译器判断使用哪个函数。
如下,函数模板重载的例子:

// maximum of two int values 
inline int const& max (int const& a, int const& b) 
{     
    return a<b?b:a; 
} 
// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{
    return a<b?b:a; 
} 
// maximum of three values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b, T const& c) 
{ 
    return max (max(a,b), c); 
}

int main() 
{ 
    ::max(7, 42, 68);     // calls the template for three arguments 
    ::max(7.0, 42.0);     // calls max<double> (by argument deduction) 
    ::max('a', 'b');      // calls max<char> (by argument deduction) 
    ::max(7, 42);         // calls the nontemplate for two ints 
    ::max<>(7, 42);       // calls max<int> (by argument deduction) 
    ::max<double>(7, 42); // calls max<double> (no argument deduction) 
    ::max('a', 42.7);     // calls the nontemplate for two ints 
} 

上面的例子中,非模板函数可以和同名的函数模板同时存在,也可以和相同类型的函数模板实例同时存在。当所有的条件都相同时,编译器会优先选择非模板函数。
因此上面的第四个调用的是非模板函数

max(7, 42)      // both int values match the nontemplate function perfectly 

但是,如果模板更匹配就使用模板的实例化函数,如:

max(7.0, 42.0)  // calls the max<double> (by argument deduction) 
max('a', 'b');  // calls the max<char> (by argument deduction) 

调用时可以使用空的模板参数列表”<>”,这种形式告诉编译器必须使用从函数模板的实例,且模板参数由调用参数自动推导。

max<>(7, 42)    // calls max<int> (by argument deduction) 

由于模板是不能进行自动类型转换的,而普通函数是可以自动类型转换,所有最后一个调用的是非模板函数。

max('a', 42.7)      // only the nontemplate function allows different argument types 

一个更有用的例子是,为指针类型和C-style的字符串重载max()模板

#include <iostream> 
#include <cstring> 
#include <string> 

// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 

// maximum of two pointers 
template <typename T> 
inline T* const& max (T* const& a, T* const& b) 
{ 
    return *a < *b ? b : a; 
} 

// maximum of two C-strings 
inline char const* const& max (char const* const& a, 
                               char const* const& b) 
{ 
    return std::strcmp(a,b) < 0 ? b : a; 
} 

int main () 
{ 
    int a=7; 
    int b=42; 
    ::max(a,b);      // max() for two values of type int 

    std::string s="hey"; 
    std::string t="you"; 
    ::max(s,t);      // max() for two values of type std::string 

    int* p1 = &b; 
    int* p2 = &a; 
    ::max(p1,p2);    // max() for two pointers 

    char const* s1 = "David"; 
    char const* s2 = "Nico"; 
    ::max(s1,s2);    // max() for two C-strings 
} 

上面的所有的重载函数多是传引用(by reference)。一般来讲,重载函数之间有这明显的差异,或者是参数的个数不同,或者是参数的类型明显不同,如果差异不够明显可能会出现一些意想不到的问题。
如下,以传值(by value)的方式重载一个传引用(by referenc)的max(),就无法使用三个参数的max()函数来取得C-style字符串的最大者:

// maximum of two values of any type (call-by-reference) 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 

// maximum of two C-strings (call-by-value) 
inline char const* max (char const* a, char const* b) 
{ 
    return std::strcmp(a,b) < 0 ? b : a; 
} 

// maximum of three values of any type (call-by-reference) 
template <typename T> 
inline T const& max (T const& a, T const& b, T const& c) 
{ 
    return max (max(a,b), c); // error, if max(a,b) uses call-by-value 
} 

int main () 
{ 
    ::max(7, 42, 68); // OK 

    const char* s1 = "frederic"; 
    const char* s2 = "anica"; 
    const char* s3 = "lucas"; 
    ::max(s1, s2, s3); // ERROR 

} 

上面的例子中,由于C-style字符串的max(a,b)重载函数创建了一个新的局部值,而改值又以引用的方式传回,就产生了错误。

上面的例子是细微的重载引发的非预期的行为。当函数调用时,如果不是所有的重载函数都在当前的范围可见,那么上述的问题可能发生,也可能不发生。如下,

// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a<b?b:a; 
} 

// maximum of three values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b, T const& c) 
{ 
    return max (max(a,b), c);  // uses the template version even for ints 
}                              // because the following declaration comes 
                               // too late: 
// maximum of two int values 
inline int const& max (int const& a, int const& b) 
{ 
    return a<b?b:a; 
} 

由于int类型的max()非模板函数定义在了调用之后,那么“return max (max(a,b), c); 调用的是函数模板的int实例。

5、总结

1、函数模板可以针对不同的模板参数定义一系列的函数。
2、函数模板根据调用参数类型,实例化不同类型的函数。
3、调用时可以显示指定目标参数。
4、函数模板也可以重载。
5、函数模板重载是,不同的重载函数之间最好存在明显的差异。
6、必须保证所有函数都定义在被调用之前。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值