先来看一下程序,看一下为什么对于模板函数,混合式调用会有如下编译错误。
//myTemplate.h
#ifndef MY_TEMPLATE_HEAD_FILE
#define MY_TEMPLATE_HEAD_FILE
#include <iostream>
template <typename T>
class Rational {
public:
Rational(const T& numerator = 0,
const T& denominator = 1);
const T numerator() const { return num; };
const T denominator() const { return denom; };
private:
T num;
T denom;
};
template <typename T>
Rational<T>::Rational(const T& numerator,
const T& denominator) :
num(numerator),
denom(denominator) {
std::cout << "Rational is constructed!! " << std::endl;
}
template <typename T>
const Rational<T> operator * (const Rational<T> lhs,
const Rational<T> rhs) {
T numerator = lhs.numerator() * rhs.numerator();
T denominator = lhs.denominator() * rhs.denominator();
Rational<T> myObj(numerator,denominator);
return myObj;
}
#endif // MY_TEMPLATE_HEAD_FILE
//testMain.cpp
#include "myTemplate.h"
int main() {
Rational<int> oneQuarter(1,4);
Rational<int> Result = oneQuarter * 2;
std::cout << "The result is " << Result.numerator() << "/" << Result.denominator() << std::endl;
return 0;
}
编译结果为:
||=== Build: Debug in nonMemberTemplate (compiler: GNU GCC Compiler) ===|
File Line Message
testMain.cpp| | In function 'int main()':|
testMain.cpp| 6 | error: no match for 'operator*' in 'oneQuarter * 2'|
testMain.cpp| 6 | note: candidate is:|
myTemplate.h| 27 | note: template<class T> const Rational<T> operator*(Rational<T>, Rational<T>)|
myTemplate.h| 27 | note: template argument deduction/substitution failed:|
testMain.cpp| 6 | note: mismatched types 'Rational<T>' and 'int'|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
通过编译结果我们可以看出,在函数模板参数类型推导时,出现了问题。
Rational<int> Result = oneQuarter * 2;
对于第一个实参,推导很顺利,oneQuarter的类型为 Rational<int>,所以T一定是int;但是对于第二个实参,推导就出现了问题。第二个实参是一个Int类型,编译器看到之后就傻眼了。你或许期盼着编译器会用用Rational<int>的构造函数将2隐式转换为Rational<int>对象,进而将T推导为Int。但这只是幼稚的想象而已。因为隐式转换只会发生在函数调用的时候,这时函数形参类型已经明确。所以,在模板函数实参推导过程中,即函数形参类型还未明确前,是不会将隐式类型转换纳入考虑范围的。
为了正确解析出模板函数的参数类型,我们可以把这个非成员普通模板函数置于Rational<T>的内部,利用实例化类的时候,将普通模板函数给顺带实例化了。而将普通函数置于类中的唯一办法就是使用friend关键字。此时,我们用friend,却与friend的传统用法,即访问类的非public成分毫不相关。
下面我们来实验一下,看看效果怎么样。
template <typename T>
class Rational {
public:
Rational(const T& numerator = 0,
const T& denominator = 1);
const T numerator() const { return num; };
const T denominator() const { return denom; };
private:
T num;
T denom;
friend const Rational<T> operator* (const Rational<T> lhs,const Rational<T> rhs);
};
编译OK,但链接时出现了问题。
undefined reference to `operator*(Rational<int>, Rational<int>)'
我们可以清楚地看到,用friend在类内部声明一个模板函数并不会,在类实例化的时候,实例化该模板函数。
为此,我们可以把该模板函数的定义移至类的内部,这样,定义在类内部的该模板函数,就自动成为一个inline函数了。这样,在类实例化的时候,模板函数也就跟着实例化了。
我们再来验证一下。
template <typename T>
class Rational {
public:
Rational(const T& numerator = 0,
const T& denominator = 1);
const T numerator() const { return num; };
const T denominator() const { return denom; };
private:
T num;
T denom;
friend const Rational<T> operator * (const Rational<T> lhs,
const Rational<T> rhs) {
T numerator = lhs.numerator() * rhs.numerator();
T denominator = lhs.denominator() * rhs.denominator();
Rational<T> myObj(numerator,denominator);
return myObj;
}
};
编译通过,运行结果为:
Rational is constructed!!
Rational is constructed!!
Rational is constructed!!
The result is 2/4
问题到此,只算是部分解决了。假如我们的operator*()函数非常复杂,不适合inline,又当如何呢?
令friend函数调用辅助函数,是推荐的做法。由于调用辅助函数,所以friend函数本身适合inline,辅助函数置于类外。
//myTemplate.h
#ifndef MY_TEMPLATE_HEAD_FILE
#define MY_TEMPLATE_HEAD_FILE
#include <iostream>
template <typename T>
class Rational {
public:
Rational(const T& numerator = 0,
const T& denominator = 1);
const T numerator() const { return num; };
const T denominator() const { return denom; };
private:
T num;
T denom;
friend const Rational<T> operator* (const Rational<T> lhs,
const Rational<T> rhs) {
return aidFunc(lhs,rhs);
}
};
template <typename T>
Rational<T>::Rational(const T& numerator,
const T& denominator) :
num(numerator),
denom(denominator) {
std::cout << "Rational is constructed!! " << std::endl;
}
template <typename T>
const Rational<T> aidFunc(const Rational<T> lhs,
const Rational<T> rhs) {
T numerator = lhs.numerator() * rhs.numerator();
T denominator = lhs.denominator() * rhs.denominator();
Rational<T> myObj(numerator,denominator);
return myObj;
}
#endif // MY_TEMPLATE_HEAD_FILE
//testMain.cpp
#include "myTemplate.h"
int main() {
Rational<int> oneQuarter(1,4);
Rational<int> Result = oneQuarter * 2;
std::cout << "The result is " << Result.numerator() << "/" << Result.denominator() << std::endl;
return 0;
}
运行结果为:
Rational is constructed!!
Rational is constructed!!
Rational is constructed!!
The result is 2/4
作为模板函数,operator*()支持混合式乘法,是因为我们将其定义完整地放入了Rational<T>类中,而aidFunc()模板函数因为没有放入类中,则 不支持混合式乘法,但它其实也不需要。因为它只是被operator*()调用,而operator*()传给aidFunc()的实参已经是隐式转换过的、两个相同的Rational对象了。即只要operator*()支持混合式操作即可。本质上operator*()支持混合类型转换就OK了。
写在题外的话
通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。即在Rational<T>的内部,friend const Rational<T> operator* (const Rational<T> lhs, const Rational<T> rhs) 可以写成 friend const Rational operator* (const Rational lhs, const Rational rhs) 。
完美Done!!!