1.在类外定义的模板函数,在实参具现化时不进行隐式类型转换:可以在函数调用过程中进行这样的转换,但是在能够调用一个函数之前,编译器必须知道那个函数存在,而为了知道它,必须先为相关的函数模板具现化参数类型。这是template C++与面向对象的C++不同的地方。
对条款24中的例子进行模板化:
template<typename T>
class Rational{
public:
Rational(const T& numerator = 0, const T& denominator = 1); //传引用
const T numerator() const;
const T denominator() const; //传值,并且是const
...
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){...} //在类外定义函数重载操作符,条款20有详细说明
Rational<int> oneHalf(1, 2);
Rational<int> result = onehalf * 2; //错误,不能通过编译!无法确知第二个参数的具体类型,因而无法进行隐式类型转换
对于这个例子而言,第一个参数是一个模板类生成的对象,可以在模板类中定义这个重载函数,这样在第一个对象具现化时,能够将该模板函数也具现化,编译器得知第二个参数的类型,从而可以完成隐式类型转换。可以将operator*声明为友元函数:
template<typename T>
class Rational{
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs); //声明为友元函数,但不在类内定义,从而连接器无法找到该函数定义
...
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) //定义。不能通过连接!
{
...
}
这里,同样的代码能够通过编译,但是不能通过连接,解决方法有二:
(1)将定义式放到类内,作为一个内联函数:
template<typename T>
class Rational{
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numberator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
...
};
(2)在类外声明并定义一个辅助的函数模板完成实际工作,在类内用函数模板调用:
template<typename T> class Rational; //前向声明
template<typename T> const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs) //许多编译器要求模板定义式必须在头文件内
{
...
}
template<typename T>
class Rational{
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return doMultiply(lhs, rhs);
}
...
};