函数模板
函数模板是C++模板机制中的一种,其作用是为了不同类型的数据生成操作相同或者相似的函数
函数模板的实现
模板以关键字typeplate开通,其后是一对以<>
划分的模板参数列表。模板参数列表可以声明多个模板参数,多个参数声明之间以逗号风格。
template<typename T>
inline T const &max(T const &a, T const &b){
return a < b ? a : b;
}
模板参数有多种类型,上面我们用到了类型模板参数
类型模板参数以typename或者class标记,后接参数名。这两个关键字在标记模板参数类型时完全等价,都表示参数为类型参数。参数列表之后为模板的内容。上例中声明的是一个函数模板,其后内容是一个函数声明。
函数模板中的函数体声明和普通函数的写法完全相同,并且,模板参数列表中所声明的类型模板参数都可以当作一个已知类型使用。
如何使用函数模板
模板可以指导编译器为特定的类型自动生成所需的函数或者数据结构。
函数模板的使用方法如下:
#include <iostream>
int main(int argc, char **argv) {
int i = 42;
std::cout << "max(7,i): " << ::max<int>(7,i) << std::endl;
double f1 = 3.4;
double f2 = -6.7;
std::cout << "max(f1,f2): " << ::max<double>(f1,f2) << std::endl;
std::string s1 = "mathematics";
std::string s2 = "math";
std::cout << "max(s1,s2): " << ::max<std::string>(s1,s2) << std::endl;
}
从上面可以看出:
- 每次调用实参类型都不同:由
int
,double
,std::string
- max模板前面的
::
,是为了确认我们调用的是全局名字空间中的max(因为标准库中也有一个std::max()模板)
可以像调用一个普通函数一样调用函数模板。
- 不同之处在于,调用函数模板时需要指定模板参数的值,其值为具体类型如int、char或者用户自定义的类。
- 函数参数的值在函数模板名后接
<>
内声明。
原理:
- 对于不同类型的参数调用,都会从模板中产生出一个不同的实体
对于int
,会自动实例化模板
inline int const &max(int const &a, int const &b){
return a < b ? a : b;
}
对于double
,会自动实例化模板
inline double const &max(double const &a, double const &b){
return a < b ? a : b;
}
对于std::string
,,会自动实例化模板
inline std::string const &max(std::string const &a, std::string const &b){
return a < b ? a : b;
}
对于不支持的参数,会产生一个编译期错误
- 根据所定义的模板参数值以及完整的函数模板声明,编译器可以自动生成一个对所需数据类型进行操作的函数,称为函数模板实例。
总结:模板被编译了两次,分别在:
- 实例化之前:检测模板代码本身,查看语法是否正确
- 实例化期间,检测模板代码,查看是否所有调用都有效
函数模板是C++模板机制的一种,其作用是为不同类型的数据生成操作相同或者相似的函数。
- 可见,虽然C++无法像弱类型语言那样一个函数处理各种类型数据,但为各种类型写相似代码的重复工作,可以借助模板交给编译器去完成。
- 并且,由于是在编译期见完成依模板生成函数并链接的工作,与之相关的函数调用都是静态调用。较之弱类型语言在运行时查看数据类型的动态方法,模板生成的静态调用其运行效率更改。
模板参数的自动类型推导
在C++中实现了一种无需显式指向,可以直接根据调用时的实参类型推导出模板参数值的功能。凡是可以直接推导出的模板参数的值,就无需在模板实参列表中写明:
#include <iostream>
int main(int argc, char **argv) {
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;
}
利用模板参数推导时需要注意以下几点:
- 编译器只根据函数调用时给出的实参列表来推导函数参数值,与函数参数类型无关的模板参数其值无法推导
- 与函数返回值相关的模板参数其值也无法推导
- 所有可推导模板参数必须时连续位于模板参数列表的尾部,中间不能有不可推导的模板参数
看个例子:
#include <iostream>
template <typename T0,
typename T1,
typename T2,
typename T3,
typename T4>
T2 func(T1 v1, T3 v3, T4 v4); // 声明一个函数模板
int main(int argc, char **argv) {
double sv2;
sv2 = func<int * , int, double >(1, 2, "v4");
std::cout << "\t\t\tsv: " << sv2 << "\n";
sv2 = func<std::nullptr_t , int, int>(1, "v3", 3.0);
std::cout << "\t\t\tsv: " << sv2 << "\n";
sv2 = func<size_t , int, int>(1, "0.2", 0.3);
std::cout << "\t\t\tsv: " << sv2 << "\n";
sv2 = func<std::nullptr_t, std::string , int>("0.1", 0.2, 0.3);
std::cout << "\t\t\tsv: " << sv2 << "\n";
}
template <typename T0,
typename T1,
typename T2,
typename T3,
typename T4>
T2 func(T1 v1, T3 v3, T4 v4){
T2 static sv2 = T2();
std::cout << "\t\t\tv1: " << v1
<< "\t\t\tv3: " << v3
<< "\t\t\tv4: " << v4;
return sv2;
}
上例中实现了一个函数模板,可以知道这个模板接受5个模板参数,依次为T0 ~ T4,其中用于声明函数参数类型的模板参数为T1, T3,T4。
- T2用于声明函数的返回值,
- T0与函数参数和返回值类型都不相干。编译器在遇到main函数中的func函数模板的调用时,根本无法从实参类型中推导出T0的默认值,所以T0的值必须在模板实参中给定。
- 至于T2,虽然与函数返回值相关,似乎能够在某些情况下反推出T2的值,比如
double d = func<...>()
,是否d=T2就是一个double呢?由于C++中存在内建类型自动类型转换(还有用户自定义的类型转换),double类型可以接受double、int、char等常量/变量的值。如果不在模板参数列表中显式给定函数返回值类型,则上面代码就会由歧义。 - 因为T2必须给定,而函数形参与实参之间只能通过位置相关联,所以T1也必须给定。C++并不支持类似
func<double, , int>
的方式来跳过对T1的赋值
因此,上面func调用时,模板实参列表只可将T3、T4省略,而T0,T1,T2不能省略
实参的演绎
函数模板的参数由我们传递的实参来决定, 这里没有自动类型转换,每个T都必须正确
由三种方法可以用来处理上面的错误:
- 对实参进行强制类型转换,使它们可以互相匹配…
::max(static_cast<double >(1),1.2)
- 显示限定T的类型
::max<int>(1,1.2)
- 显示的 指定两个参数可以有不同的类型。
- 返回类型是某个函数参数类型
template<typename T1, typename T2>
inline T1 const &max(T1 const &a, T2 const &b){
return a < b ? a : b;
}
- 返回类型和函数参数类型没有关系
template<typename RT, typename T1, typename T2>
inline RT const &max(T1 const &a, T2 const &b){
return a < b ? a : b;
}
//--------------使用-------------
::max<int, double, double>(1,1.1)
::max<double>(1,1.1)
模板参数的默认值
最新的C++11标准运行为函数模板参数赋默认值。比如:
#include <iostream>
template <typename T0 = float ,
typename T1,
typename T2 = float ,
typename T3,
typename T4>
T2 func(T1 v1, T3 v3, T4 v4); // 声明一个函数模板
int main(int argc, char **argv) {
double sv2;
sv2 = func(1, 2, "v4");
std::cout << "\t\t\tsv: " << sv2 << "\n";
sv2 = func(1, "v3", 3.0);
std::cout << "\t\t\tsv: " << sv2 << "\n";
}
template <typename T0,
typename T1,
typename T2,
typename T3,
typename T4>
T2 func(T1 v1, T3 v3, T4 v4){
T2 static sv2 = T2();
std::cout << "\t\t\tv1: " << v1
<< "\t\t\tv3: " << v3
<< "\t\t\tv4: " << v4;
return sv2;
}
重载函数模板
#include <iostream>
#include <max.h>
#include <complex>
// 传引用
inline int const& max (int const& a, int const& b)
{
return a < b ? b : a;
}
// 传引用
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(int argc, char **argv) {
::max(7, 42, 68); // 调用具有3个参数的模板
::max(7.0, 42.0); // 通过实参演绎调用max<double>
::max('a', 'b'); //通过实参演绎调用max<char>
::max(7, 42); //调用int重载的非模板函数
::max<>(7, 42); //通过实参演绎调用max<int>
::max<double>(7.0, 42.0); // 通过实参演绎调用max<double>
::max('a', 42.7); //调用int重载的非模板函数
}
分析:
- 一个非模板函数和一个同名模板函数可以同时存在,而且该函数模板还可以被实例化为这个非模板参数
- 对于非模板函数和同名函数模板,会优先调用非模板函数,而不是从模板函数中示例一个模板
::max(7, 42); //调用int重载的非模板函数
- 如果模板可以产生一个更匹配的函数,会选择模板
::max(7.0, 42.0); // 通过实参演绎调用max<double>
::max('a', 'b'); //通过实参演绎调用max<char>
- 可以显示的指定一个空的模板实参列表,告诉编译器:只有模板才能匹配这个函数
::max<>(7, 42); //通过实参演绎调用max<int>
- 因为模板是不允许自动类型转换的,但是普通函数可以找到类型转换
::max('a', 42.7); //调用int重载的非模板函数
注意:
- 什么时候需要重载模板函数?
- 改变参数的数目
- 显示的指定模板参数
- 函数的所有重载版本的声明都应该位于该函数被调用的位置之前
总结
- 模板函数为不同的模板实参定义了一个函数家族
- 当传递模板实参的时候,可以根据实参的类型来对函数模板进行实例化
- 可以显示的指定模板参数
- 可以重载函数模板用于:
- 改变参数的数目
- 显示的指定模板参数
- 函数的所有重载版本的声明都应该位于该函数被调用的位置之前
更多推荐
C/C++编程:函数模板
发布评论