需要类型转换时请为模板定义非成员函数——条款46
条款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相关推荐
- 条款46:需要类型转换的时候请为模板定义非成员函数
看看下面这个例子: 1 template<typename T> 2 class Rational{ 3 public: 4 Rational(const T & numerato ...
- 【C++模板编程入门】模板介绍、模板定义、函数模板、类模板、模板的继承
1.模块的引入 1.1.示例代码 #include <iostream> #include <string>using namespace std;//用template声明T ...
- 定义python函数时如果没有return_定义 Python 函数时,如果函数中没有 return 语句,则默认返回空值 None 。_学小易找答案...
[多选题]因发现核酶而共享诺贝尔化学奖的科学家是(). [简答题]如果是六角梅花,你还可以用什么方法完成? [填空题]如果函数中没有 return 语句或者 return 语句不带任何返回值,那么该函 ...
- C++ - 模板函数须要类型转换时使用友元(friend)模板函数
模板函数须要类型转换时使用友元(friend)模板函数 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24357301 非模板函数 ...
- 函数模板与类模板定义和使用
模板是将具有相似性的类和函数归纳起来构成一个类族或函数族,它可是程序具有通用性.模板分为类模板和函数模板. 目录 (一)函数模板 一般定义形式 模板函数重载 函数模板参数 带有多类型参数的函数模板 ( ...
- FME写入Excel数据时写到模板文件指定位置
在写入Excel数据时写到模板文件指定位置 介绍 本示例对 Excel 写模块参数概述一文进行了扩展.在该示例中,您学习了如何更新模板文件指定的单元格. 使用 FME,您可以重写 RawData 工作 ...
- Vim 自动文件头注释与模板定义
Vim 自动文件头注释与模板定义 在vim的配置文件.vimrc添加一些配置可以实现创建新文件时自动添加文件头注释,输入特定命令可以生成模板. 使用方法 插入模式输入模式输入seqlogic[Ente ...
- C++ Primer 学习笔记_75_模板与泛型编程 --模板定义
模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...
- C++ Primer 5th笔记(chap 16 模板和泛型编程)类模板定义
1. 定义 类似函数模板,类模板以关键字template开始,后跟模板参数列表.在类模板(及其成员)的定义中,我们将模板参数当作替身,代替使用模板时用户需要提供的类型或值: template < ...
最新文章
- C++对象内存布局--⑤GCC编译器--单个虚拟继承
- spring核心功能结构
- MySQL下载以及安装【windows】
- Linux学习之系统编程篇:读写锁(pthread_ rwlock _init / rdlock / wrlock / unlock / destroy)
- DXSDK_June10安装错误
- OpenFire源码学习之二十一:openfie对用户的优化(上)
- [西瓜书习题] 第二章 模型评估与选择
- a3967驱动_以A3967SLB为核心的步进电机控制系统设计
- 我的世界服务器开启就停止运行,我的世界怎么停止时间
- 《众妙之门 JavaScript与jQuery技术精粹》 - 读书笔记总结[无章节版][1-60]
- 网站服务器建立数据库连接时出错,修复Wordpress博客网站“建立数据库连接时出错”错误记录 | 科技爱好者博客 -专注于树莓派(Raspberry Pi)...
- CTF-web题之简单的SQL注入
- javascript之广告效果
- android 反编译 签名,Android反编译及重签名命令
- wps怎么免费导出简历_WPS表格办公—一键添加简历模板
- 不要轻易在简历上写我热爱编程,我热爱学习—兄弟连IT教育
- python两个列表的差集_Python求两个list的差集、交集与并集的方法
- 怎么用python表白_如何正确使用Python进行表白
- 如何通过命令行使Linux设备进行网页认证(WEB认证)
- Selenium键鼠事件_Sinno_Song_新浪博客
热门文章
- iOS开发中常用的那些工具
- 失业下的深圳中年:没有人活的容易,生活仍得继续...
- 联志服务器修改启动项,双塔奇兵,来自联志的两款服务器机箱
- HCIP-DATACOM-解题九月部分58+51题
- java秋招面试攻略
- 【Spring Boot】21.集成elasticsearch
- Arduino应用开发——SD卡
- 天猫HTMl静态页面
- 丝芙兰(Sephora)和悦诗风吟(Innisfree)如何用“购物篮”改善顾客购物体验
- 淘宝/天猫API接口,买家卖家订单信息获取