非成员模板函数,混合式调用问题

先来看一下程序,看一下为什么对于模板函数,混合式调用会有如下编译错误。

//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!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值