本节书摘来自异步社区出版社《C++ Templates中文版》一书中的第2章,第2.4节,作者: 【美】David Vandevoorde , 【德】Nicolai M. Josuttis,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.4 重载函数模板
和普通函数一样,函数模板也可以被重载。就是说,相同的函数名称可以具有不同的函数定义;于是,当使用函数名称进行函数调用的时候,C++编译器必须决定究竟要调用哪个候选函数。即使在不考虑模板的情况下,做出该决定的规则也已经是相当复杂,但在这一节里,我们将讨论有关模板的重载问题。如果你对不含模板的重载的基本规则还不是很熟悉,那么请先阅读附录B,在那里我们对重载解析规则进行了很详细的叙述。
下面的简短程序叙述了如何重载一个函数模板:
//basics/max2.cpp
//求两个int值的最大值
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;
}
// 求3个任意类型值中的最大者
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); // 调用具有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, 42); //调用max<double> (没有实参演绎)
::max('a', 42.7); //调用int重载的非模板函数
}
如例子所示,一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。对于非模板函数和同名的函数模板,如果其他条件都是相同的话,那么在调用的时候,重载解析过程通常会调用非模板函数,而不会从该模板产生出一个实例。第4个调用就符合这个规则:
max(7,42) //使用两个int值,很好地匹配非模板函数
然而,如果模板可以产生一个具有更好匹配的函数,那么将选择模板。这可以通过max()的第2次和第3次调用来说明:
max(7.0,42.0); //调用 max<double>(通过实参演绎)
max('a', 'b'); //调用 max<char>(通过实参演绎)
还可以显式地指定一个空的模板实参列表,这个语法好像是告诉编译器:只有模板才能来匹配这个调用,而且所有的模板参数都应该根据调用实参演绎出来:
max<>(7,42) //call max<int>(通过实参演绎)
因为模板是不允许自动类型转化的;但普通函数可以进行自动类型转换,所以最后一个调用将使用非模板函数(‘a’和42.7都被转化为int):
max('a',42.7) //对于不同类型的参数,只允许使用非模板函数
下面这个更有用的例子将会为指针和普通的C字符串重载这个求最大值的模板:
//basics/max3.cpp
#include <iostream>
#include <cstring>
#include <string>
// 求两个任意类型值的最大者
template <typename T>
inline T const& max (T const& a, T 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;
}
// 求两个C字符串的最大者
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() 求两个int值的最大值
std::string s="hey";
std::string t="you";
::max(s,t); // max() 求两个std:string类型的最大值
int* p1 = &b;
int* p2 = &a;
::max(p1,p2); // max() 求两个指针所指向值的最大者
char const* s1 = "David";
char const* s2 = "Nico";
::max(s1,s2); // max() 求两个c字符串的最大值
}
注意,在所有重载的实现里面,我们都是通过引用来传递每个实参的。一般而言,在重载函数模板的时候,最好只是改变那些需要改变的内容;就是说,你应该把你的改变限制在下面两种情况:改变参数的数目或者显式地指定模板参数。否则就可能会出现非预期的结果。例如,对于原来使用传引用的max()模板,你用C-string类型进行重载;但对于现在(即重载版本的)基于C-strings的max()函数,你是通过传值来传递参数;那么你就不能使用3个参数的max()版本,来对3个C-string求最大值:
//basics/max3a.cpp
#include <iostream>
#include <cstring>
#include <string>
// 两个任意类型值的最大者 (通过传引用进行调用)
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}
// 两个C字符串的最大者 (通过传值进行调用)
inline char const* max (char const* a, char const* b)
{
return std::strcmp(a,b) < 0 ? b : a;
}
// 求3个任意类型值的最大者 (通过传引用进行调用)
template <typename T>
inline T const& max (T const& a, T const& b, T const& c)
{
return max (max(a,b), c); //注意:如果max(a,b)使用传值调用
//那么将会发生错误
}
int main ()
{
::max(7, 42, 68); // OK
const char* s1 = "frederic";
const char* s2 = "anica";
const char* s3 = "lucas";
::max(s1, s2, s3); // 错误。
}
问题在于:如果你对3个C-strings调用max(),那么语句:
return max (max(a,b),c);
将会产生一个错误。这是因为对于C-strings而言,这里的max(a,b)产生了一个新的临时局部值,该值有可能会被外面的max函数以传引用的方式返回,而这将导致传回无效的引用。
对于复杂的重载解析规则所产生的结果,这只是具有非预期行为的代码例子中的一例而已。例如,当调用重载函数的时候,调用结果就有可能与该重载函数在此时可见与否这个事实有关,但也可能没有关系。事实上,定义一个具有3个参数的max()版本,而且直到该定义处还没有看到一个具有两个int参数的重载max()版本的声明;那么这个具有3个int实参的max()调用将会使用具有2个参数的模板,而不会使用基于int的重载版本max():
//basics/max4.cpp
// 求两个任意类型值的最大者
template <typename T>
inline T const& max (T const& a, T const& b)
{
return a < b ? b : a;
}
// 求3个任意类型值的最大者
template <typename T>
inline T const& max (T const& a, T const& b, T const& c)
{
return max (max(a,b), c); //使用了模板的版本,即使有下面声明的int
//版本,但该声明来得太迟了
}
// 求两个int值的最大者
inline int const& max (int const& a, int const& b)
{
return a < b ? b : a;
}
我们将在9.2节讨论这个细节;但就目前而言,你应该牢记一条首要规则:函数的所有重载版本的声明都应该位于该函数被调用的位置之前。