46:需要类型转换时请为模板定义非成员函数

先看下面这段代码:

template <typename T>
class Rational {
public:
    Rational(const T& numerator = 0, const T& denominator = 1);
    const T numerator() const;
    const T denominator() const;
    //...
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){/*...*/ }

再看下面这段代码:

Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2;//错误,无法通过编译

上面两段代码是文章24中的代码。

上述失败给我们的启示是,模板化的Rational内的某些东西似乎和其non-template版本不同。在文章24,编译器知道我们尝试调用什么函数,但这里编译器不知道我们想要调用哪个函数。取而代之的是,它们试图想出什么函数被名为operator*的template具现化出来。它们知道它们应该可以具现化某个“名为operator*并接受两个Rational<T>参数”的函数,但为完成这一具现化行动,必须先算出T是什么,问题是它们没有这个能耐。

以ondHalf进行推导,过程并不困难。operator*的第一参数被声明为Rational <T>,而传递给operator*的第一参数(oneHalf)的类型是Rational<int>,所以T一定是int。

其他参数的推导则没有这么顺利。operator*的第二参数被声明为Rational<T>,但传递给operator*的第二实参类型是int。编译器如何根据这个推算出T?你或许会期盼编译器使用Rational<int>的non-explicit构造函数将int转换为Rational<int>,进而将T推导为int,但它们不那么做,因为在template实参推导过程中从不将隐式类型转换函数纳入考虑。这样的转换在函数调用过程中的确被使用,但在能够调用一个函数之前,首先必须知道那个函数存在。而为了知道它,必须先为相关的function template推导出参数类型(然后才可将适当的函数具现化出来)。然而,template实参推导过程中并不考虑采纳“通过构造函数而发生的”隐式类型转换。

只要利用一个事实,就可以缓和编译器在template实参推导方面受到的挑战:template class内的friend声明式可以指涉某个特定函数。这意味class Rational<T>可以声明operator*是它的一个firend函数。class template并不倚赖template实参推导(后者只施行于function template身上),所以编译器总是能够在class Rational<T>具现化时得知T。

因此,令Rational<T> class声明适当的operator*为其friend函数,可简化整个问题:

template <typename T>
class Rational {
public:
    //...
    //声明operator*函数
    friend const Rational operator* (const Rational<T>& lhs, const Rational<T>& rhs);
};
//定义operator*函数
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){/*...*/ }

现在对operator*的混合式调用就可以通过编译了。因为当对象oneHalf被声明为一个Rational<int>,class Rational<int>于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational<int>参数)也就被自动声明出来。后者身为一个函数而非函数模板,因此编译器可在调用它时使用隐式转换函数(例如Rational的non-explicit构造函数),而这便是混合式调用之所以成功的原因。

虽然上述代码能够通过编译,却无法连接。稍后解决这个问题。

首先谈谈在Rational内声明operator*的语法。

在一个class template内,template名称可被用来作为“template和其参数”的简略表达方式,所以在Rational<T>内可以只写Rational而不必写Rational<T>。

本例中的operator*被声明为接受并返回Rational(而非Rational<T>)。若它被声明如下,一样有效:

template <typename T>
class Rational {
public:
    //...
    //声明operator*函数
    friend const Rational<T> 
        operator* (const Rational<T>& lhs, const Rational<T>& rhs);
    //...
};

现在想想遇到的问题。混合式代码通过了编译,因为编译器知道我们要调用那个函数,但那个函数只被声明于Rational内,并没有被定义出来。我们意图令此class外部的operator* template提供定义式,但行不通。若我们自己声明了一个函数(那正是Rational template内的作为),就有责任定义那个函数,既然我们没有提供定义式,连接器当然找不到它。

最简单的可行办法就是将opeartor*函数本体合并至声明式内:

template <typename T>
class Rational {
public:
    //...
    friend const Rational<T> 
        operator* (const Rational<T>& lhs, const Rational<T>& rhs)
    {
        return Rational(lhs.numerator() * rhs.numberator(), 
            lhs.denominator() * rhs.denominator());
    }
    //...
};

这项技术的一个趣味点是,我们虽然使用friend,却与friend的传统用途“访问class的non-public成分”毫不相干。为了让类型转换可能发生于所有实参身上,我们需要一个non-member函数。为了令这个函数被自动具现化,我们需要将它声明在class内部,而在class内部声明non-member函数的唯一办法就是:令它成为一个friend。

“Rational是个template”这一事实意味上述的辅助函数通常也是个template,所以定义了Rational的头文件代码,很典型地长这个样子:

template<typename T> class Rational;//声明Rational template
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);
template<typename T>
class Rational {
public:
    //...
    friend const Rational<T>
        operator* (const Rational<T>& lhs, const Rational<T>& rhs)
    {
        //令friend调用helper
        return doMultiply(lhs, rhs);
    }
    //...
};

许多编译器实质上会强迫你把所有template定义式放进头文件内,所以你或许需要在头文件内定义doMultiply,看起来像这样:

//若有必要,在头文件内定义helper template
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
    return Rational<T>(lhs.numerator() * rhs.numberator(),
        lhs.denominator() * rhs.denominator());
}

作为一个template,doMultiply当然不支持混合式乘法,但它也不需要。它只被operator*调用,而operator*支持了混合式操作。本质上,operator*支持了类型转换所需的任何东西,确保两个Rational对象能够相乘,然后它将这两个对象传给一个适当的doMultiply template具现体,完成实际的乘法操作。

总结

当我们编写一个class template,而它所提供的“与此template相关的”函数支持“所有参数的隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值