条款24讨论过为什么唯有non-member函数才有能力“在所有实参身上实施隐式类型转换”,该条款并以Rational class的operator*函数为例。我强烈建议你继续看下去之前先让自己熟稔那个例子,因为本条款首先以一个看似无害的改动扩充条款24的讨论:本条款将Rational和operator*模板化了:

template<typename T>class Rational {public:Rational(const T& numberator = 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)
{ ... }

像条款24一样,我们希望支持混合式算数运算,所以我们希望以下代码顺利通过编译。我们也预期它会,因为它正是条款24所列的同一份代码,唯一不同的是Rational和operator*如今都成了templates:

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

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

为了推导T,它们看了看operator*调用动作中的实参类型。本例中那些类型分别是Rational<int>(oneHalf的类型)和int(2的类型)。每个参数分开考虑。

以oneHalf进行推导,过程并不困难。operator*的第一个参数被声明为Rational<T>,而传递给operator*的第一实参(oneHalf)的类型是Rational<int>,所以T一定是int。其他参数的推导则没有这么顺利。operator*的第二参数被声明为Rational<T>,但传递给operator*的第二实参(2)类型是int。编译器如何根据这个推算出?你或许会期盼编译器使用Rational<int>的non-explicit构造函数将2转换为Rational<int>,进而将T推导为int,但它们不那么做,因为在template实参推导过程中从不将隐式类型转换函数纳入考虑。

只要利用一个事实,我们就可以缓和编译器在template实参推导方面受到的挑战:template class内的friend声明式可以指涉某个特定函数。那意味Rational<T>可以声明operator*是它的一个friend函数。Class templates并不依赖template实参推导(后者只施行于function templates身上),所以编译器总是能够在class Rational<T>具现化时得知T。因此,令Rational<T> class声明适当的operator*为其friend函数,可简化整个问题:

template<typename T>class Rational {public:...friendconst Rational operator*(const Rational& lhs, const Rational& rhs);
};
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*被声明为接受并返回Rationals(而非Rational<T>s)。如果它被声明如下,一样有效:

template<typename T>class Rational {public:...friendconst Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);...
};

然而使用简略表达式比较轻松也比较普遍。

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

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

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

这便如同我们所期望地正常运作了起来:对operator*的混合式调用现在可以编译并执行。

请记住

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

需要类型转换时请为模板定义非成员函数——条款46相关推荐

  1. 条款46:需要类型转换的时候请为模板定义非成员函数

    看看下面这个例子: 1 template<typename T> 2 class Rational{ 3 public: 4 Rational(const T & numerato ...

  2. 【C++模板编程入门】模板介绍、模板定义、函数模板、类模板、模板的继承

    1.模块的引入 1.1.示例代码 #include <iostream> #include <string>using namespace std;//用template声明T ...

  3. 定义python函数时如果没有return_定义 Python 函数时,如果函数中没有 return 语句,则默认返回空值 None 。_学小易找答案...

    [多选题]因发现核酶而共享诺贝尔化学奖的科学家是(). [简答题]如果是六角梅花,你还可以用什么方法完成? [填空题]如果函数中没有 return 语句或者 return 语句不带任何返回值,那么该函 ...

  4. C++ - 模板函数须要类型转换时使用友元(friend)模板函数

    模板函数须要类型转换时使用友元(friend)模板函数 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24357301 非模板函数 ...

  5. 函数模板与类模板定义和使用

    模板是将具有相似性的类和函数归纳起来构成一个类族或函数族,它可是程序具有通用性.模板分为类模板和函数模板. 目录 (一)函数模板 一般定义形式 模板函数重载 函数模板参数 带有多类型参数的函数模板 ( ...

  6. FME写入Excel数据时写到模板文件指定位置

    在写入Excel数据时写到模板文件指定位置 介绍 本示例对 Excel 写模块参数概述一文进行了扩展.在该示例中,您学习了如何更新模板文件指定的单元格. 使用 FME,您可以重写 RawData 工作 ...

  7. Vim 自动文件头注释与模板定义

    Vim 自动文件头注释与模板定义 在vim的配置文件.vimrc添加一些配置可以实现创建新文件时自动添加文件头注释,输入特定命令可以生成模板. 使用方法 插入模式输入模式输入seqlogic[Ente ...

  8. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  9. C++ Primer 5th笔记(chap 16 模板和泛型编程)类模板定义

    1. 定义 类似函数模板,类模板以关键字template开始,后跟模板参数列表.在类模板(及其成员)的定义中,我们将模板参数当作替身,代替使用模板时用户需要提供的类型或值: template < ...

最新文章

  1. C++对象内存布局--⑤GCC编译器--单个虚拟继承
  2. spring核心功能结构
  3. MySQL下载以及安装【windows】
  4. Linux学习之系统编程篇:读写锁(pthread_ rwlock _init / rdlock / wrlock / unlock / destroy)
  5. DXSDK_June10安装错误
  6. OpenFire源码学习之二十一:openfie对用户的优化(上)
  7. [西瓜书习题] 第二章 模型评估与选择
  8. a3967驱动_以A3967SLB为核心的步进电机控制系统设计
  9. 我的世界服务器开启就停止运行,我的世界怎么停止时间
  10. 《众妙之门 JavaScript与jQuery技术精粹》 - 读书笔记总结[无章节版][1-60]
  11. 网站服务器建立数据库连接时出错,修复Wordpress博客网站“建立数据库连接时出错”错误记录 | 科技爱好者博客 -专注于树莓派(Raspberry Pi)...
  12. CTF-web题之简单的SQL注入
  13. javascript之广告效果
  14. android 反编译 签名,Android反编译及重签名命令
  15. wps怎么免费导出简历_WPS表格办公—一键添加简历模板
  16. 不要轻易在简历上写我热爱编程,我热爱学习—兄弟连IT教育
  17. python两个列表的差集_Python求两个list的差集、交集与并集的方法
  18. 怎么用python表白_如何正确使用Python进行表白
  19. 如何通过命令行使Linux设备进行网页认证(WEB认证)
  20. Selenium键鼠事件_Sinno_Song_新浪博客

热门文章

  1. iOS开发中常用的那些工具
  2. 失业下的深圳中年:没有人活的容易,生活仍得继续...
  3. 联志服务器修改启动项,双塔奇兵,来自联志的两款服务器机箱
  4. HCIP-DATACOM-解题九月部分58+51题
  5. java秋招面试攻略
  6. 【Spring Boot】21.集成elasticsearch
  7. Arduino应用开发——SD卡
  8. 天猫HTMl静态页面
  9. 丝芙兰(Sephora)和悦诗风吟(Innisfree)如何用“购物篮”改善顾客购物体验
  10. 淘宝/天猫API接口,买家卖家订单信息获取