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