Re: [问题] template ostream

楼主: LPH66 (-6.2598534e+18f)   2017-06-09 05:15:01
※ 引述《moebear (萌熊)》之铭言:
: 开发平台(Platform): (Ex: Win10, Linux, ...)
: win10/linux
: 编译器(Ex: GCC, clang, VC++...)+目标环境(跟开发平台不同的话需列出)
: GCC/VC++
: 额外使用到的函数库(Library Used): (Ex: OpenGL, ...)
: 问题(Question):
: 请问程式码中第6/22/28行,这三个ostream之间的关联性是什么?
: 25行以上是助教给的程式码,但是我寄信问助教,他只说这是约定俗成的写法 囧。
: 1.为什么第6行是必备的? 我的理解中,提前宣告是因为实作在后面,中间可能有人用到
: 但是中间到底是谁用到呢? 22行吗? 那为什么22行会需要用到第6行的宣告呢?
: 2.第22行的<>是什么意思呢? 我觉得看起来很像是某种template,
: 但是中间又不能塞T进去。
: 3.第28行是我自己写的,我试过很多方法,
: 但是好像只有这样写才可以,跟他关联的好像是第6行,而不是第22行。
: 总之就是这三行之间的关系,以及为什么22行要这样写?
先讲结论: 根本原因是 CirDequeTemplate 是一个 template class 的关系
如果你只是在写一个非 template class 的 ostream << operator 的话
事情其实很简单:
class IntCirDeque
{
//...
friend ostream& operator << (ostream&, const IntCirDeque&);
};
ostream& operator << (ostream& out, const IntCirDeque& icd)
{
//...
}
这样就行了, 甚至那行 friend 还能充当前置宣告使用
也就是对只引用 IntCirDeque 定义而没有 operator << 实作的地方也能呼叫它
(有个限制就是: 它要能被 ADL 找到才行, 这里显然 ADL 能找得到
不过这跟原问题无关就表过不提)
关于这种写法的细节你应该清楚, 例如为什么它要是非成员再写 friend, 这里略过
====
那么为什么当挂上 template 事情就有点微妙的不一样了?
首先, 先看你的实作 (#27~#28):
template<class T>
std::ostream & operator<< (std::ostream &s, const CirDequeTemplate<T> &cird)
你应该在尝试时注意到了, 因为 cird 是一个 template class
你必须要对每个 T 都要能产生一份这个 operator 才行
如你所观察到的, 跟它相关的是 #6
事实上你这两行扣掉变量名其实是必须要跟 #6 一模一样才行
这正是在达成上面提的“对每个 T 都要能产生一份这个 operator”
这个写法其实就只是一个普通的 template 函数的写法而已
跟 template class 一样, 在前面挂上 template <class T> 然后下面用 T 代换
到这里应该回答了你的问题 3
(一个题外话, 你这里的实作不能用 printf, 因为当 T 不是 int 时就爆了
你应该要 << 进那个 ostream& 里面, 跟 cout 一样的用法只不过 cout 换成这个参数
是说你都在学 C++ 了为什么还写 printf...)
====
刚才提到“对每个 T 都要能产生一份这个 operator”
所以 class 里面宣告 friend 时必须要宣告
“有跟我一样的 T 的 operator << 是我的朋友”
这里需要提一个你应该也知道的规则:
单独 template 名并不是一个完整的名字, 它一定要有办法知道它的 <> 里是什么型别
在大多数时候 <> 都不能省略, 否则编译器不会知道这个名字是个 template 名
就算你的 <> 里要用其他资讯推导也是一样
<> 能省略的地方只有你在自己里面指名自己时可以省略
(我知道这里有人要提 template 函数呼叫, 那个最后提)
(这里还必须要真的是指名自己, 也就是 template 参数都要一模一样才行
如果不一样那就还是得乖乖写出来)
这就是这里的 <> 的用途: 表示这个 operator << 是一个 template 名字
那 <> 里面的东西也不必每次都一一指定, 能够推导的就能省略
这个状况里这里面是可以塞 T 的:
friend ostream& operator << <T> (ostream&, const CirDequeTemplate<T>&);
即是重复一次 #6 / #28 的宣告
只不过把宣告用的前置 template <class T> 换成指名用的后置 <T>
但是这个 T 其实是可以从参数推导出来的:
这个宣告的第二参数是一个自己这种物件, 因此这时的 T 是确定的: 跟我自己一样的 T
因此我们可以省略前面的 operator << <T> 里的 T, 就成了 operator << <> 了
后面的 <T> 是属于上面所说指名自己时可省略的状况, 所以也就不写了
这样就成了 #22 的写法:
friend ostream& operator<< <>(ostream&, const CirDequeTemplate&);
以上回答问题 2
====
刚才也提到, #22 的写法里的 operator << 是个 template 名
既然是 template 名那在前面就必须要有对应的 template 宣告才能使用
#6 就正是这个 operator << 所需要的前置 template 宣告
有了 #6 的宣告, #22 的 operator << 才能知道是一个 template 名字
才能跟后续的 template 定义连起来
这里你不能像非 template 版本一样把 template 宣告跟 friend 合起来
原因是你所 friend 的只有这特别的一个 operator << 而已, 不是整个 template
以上回答问题 1
同样的理由 #5 为 #6 里的 CirDequeTemplate 这名字进行前置宣告
====
因此全部总结起来的话就是这样:
#28 为了 template class 所以写成一个单纯的 template 函数
#22 尝试宣告某个特定的 #28 为 friend, 为此需要 #6 前置宣告 #28 的存在
====
最后回头解释一下为什么上面没提 template 函式呼叫做为省略 <> 的状况
这是因为函式呼叫是个不一样的状况
编译器会取出这个名字, 看看有没有一般函数及 template 函数有这名
如果有看到 template 函数, 会尝试推导要用什么 template 参数才能符合呼叫方型态
所有能推导出来的版本跟(如果找得到的)一般函数再一起进行 overload resolution
(是的, 我们可以定义同一个名字有非 template 函数和 template 函数)
也就是说, 函数呼叫这个指名并不是直接指名某个 template 函式定义
因此不是属于“提到 template 名”的状况
(然后顺带一提, C++17 把这个推导推广到 constructor 呼叫也适用了
在 C++17 的文件当中叫做 deduction guide, 这里表过不提)
作者: hunandy14 (Charlott.HonG)   2017-06-09 10:07:00
呜哇 c++17那个好方便 长知识了
作者: TianBonBon (田蹦蹦)   2017-06-09 11:18:00
超猛
作者: moebear (萌熊)   2017-06-09 13:36:00
谢谢! 我懂了 题外话那边是我习惯不好,之后我多加留意
作者: phishingphi (hsnutontu)   2017-06-10 21:50:00
Effective C++ Item 46 也有类似这里的用法,不过是对 implicit type conversion 的 template 用法。可供有兴趣的人参考。
作者: xvid (DivX)   2017-06-15 20:41:00

Links booklink

Contact Us: admin [ a t ] ucptt.com