看板 C_and_CPP 關於我們 聯絡資訊
開發平台(Platform): (Ex: Win10, Linux, ...) Ubuntu 20.04 編譯器(Ex: GCC, clang, VC++...)+目標環境(跟開發平台不同的話需列出) g++ 額外使用到的函數庫(Library Used): (Ex: OpenGL, ...) 問題(Question): 給定一個 template class 全部的 member function definition,有沒有辦法對於某個 sp ecialized class 來說我只特化它的其中一個 function,而且要用 original definition? 由於我必須複製它原始整份的實作到特化的函式定義裡面,才會編譯成功。想請問各位板大 有沒有不用複製整份程式碼也能只特化其中一個函式的方法?穴穴大家。 餵入的資料(Input): 預期的正確結果(Expected Output): 錯誤結果(Wrong Output): 程式碼(Code):(請善用置底文網頁, 記得排版,禁止使用圖檔) 我現在程式碼有定義一個可以根據 Leaf type 去特化的一個二元樹模板 template <typename Leaf> struct AUTOQ::Util::BinaryTree .h 檔有放 prototype, .cpp 檔有放 implementation 那我們都知道如果要針對某種 Leaf 例如 int 去特化這棵二元樹,那必須在所有 *.cpp 的末尾加上 template struct AUTOQ::Util::BinaryTree<int>; 這句話才能把所有函式 實作特化出來。 但現在問題是,我的模板有包含一個兩棵二元樹相加的函式,而這個在 Leaf 是 string 的時候是無法支援的,因此我只想實作例如 AUTOQ::Util::BinaryTree<string> print 函式,那現在的狀況就是: (1) 單純在有實作 print 的 .cpp 底下補上 template <> void AUTOQ::Util::BinaryTree<string>::print(); 這句話,此時 main 函式會通報找不到這個實作,而無法編譯成功。 (2) 我在這個有實作 print 的 .cpp 底下補上 template <> void AUTOQ::Util::BinaryTree<string>::print() { // 複製原本模板裡面的程式碼 } 這個新的實作,main 函式就找得到了。 所以問題就是,如何採用類似 (1) 的手法,使得我不需要再複製一次程式碼,就能直接 使用模板的實作呢? 補充說明(Supplement): -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 111.83.75.207 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1676046415.A.B53.html
alan23273850: 程式碼我明天再補 02/11 00:27
LPH66: 繼承該特定 specialization 並 override 掉你要特化的函數 02/11 02:58
LPH66: 這樣可以嗎? 02/11 02:59
LPH66: 噢等等, 如果該函數沒有 virtual 那 override 抓不到 02/11 03:00
LPH66: 原程式碼呼叫被蓋掉的函數的狀況 02/11 03:00
LPH66: 不過如果你動得到模版原始碼的話, 加個 virtual 應該就行了 02/11 03:02
jack7775kimo: CRTP? 02/11 07:55
alan23273850: 我補上程式碼了,這個問題對我來說很重要,如果獲得 02/11 11:51
alan23273850: 解答的話我將奉送大量批幣! 02/11 11:51
nh60211as: 把實作放在header 02/11 12:04
alan23273850: @jack7775kimo 大那個關鍵字我剛剛查了一下好像很酷 02/11 12:07
alan23273850: 但不確定能不能用在這裡 02/11 12:07
alan23273850: @nh60211as 大的解法我可能需要更具體的理由 02/11 12:07
Fenikso: 能動header的話就用concept或enable_if處理吧 02/11 13:57
Fenikso: https://godbolt.org/z/36E585xr8 02/11 13:57
LPH66: template 其實會常見把實作寫在 header 裡的做法 02/11 14:18
LPH66: 理由是模版實現只在給定所有模版參數之後 02/11 14:19
alan23273850: 我理解 @Fenikso 大大正面表列的作法,但是這樣會有 02/11 14:19
LPH66: 除非像你這樣特別去引用一個模版把它特化出來 02/11 14:19
alan23273850: 一堆 function 要加上 requires 很不方便,更重要的 02/11 14:19
LPH66: 不然你是無法對別的 TU 裡引用的模版去產生程式碼的 02/11 14:20
alan23273850: 是,我不知道為何內文 (1) 的作法編譯器會不通過。 02/11 14:20
LPH66: 把實作寫在標頭就把很多決定模版的地方延後到使用處生成 02/11 14:21
alan23273850: 哦哦哦 但是我好像漸漸對 @Fenikso 大大的答案有點 02/11 14:25
alan23273850: 感覺了,只要實作的地方先加註 prerequisites 確保 02/11 14:26
alan23273850: 實作完畢,才開始實現我這個函式,好像就能避免掉我 02/11 14:26
alan23273850: 那個問題,我禮拜一有空會試試看,如果 OK 的話就 02/11 14:26
alan23273850: 奉送 @Fenikso 一個大禮! 02/11 14:27
alan23273850: 也謝謝 @LPH66 大大的解說,兩個我都會試試看。 02/11 14:28
wulouise: 為甚麼你要寫在source file內感覺才是癥結點 02/12 00:17
alan23273850: 一般不是都鼓勵分成兩個檔案嗎?不然理由是什麼呢 02/12 00:36
closer76: 其實你去看大部分使用template 的函式庫,都是直接把實 02/12 09:03
closer76: 作寫在 header files 裡的。原因是大部分的編譯器都不支 02/12 09:03
closer76: 援有 template 的類別、函式內容另外定義。 02/12 09:03
closer76: 其實你可以用編譯器的角度想想:template 其實就是編譯 02/12 09:15
closer76: 器要幫你特化,所以編譯器需要知道 template 的原始碼。 02/12 09:15
closer76: 而 C++ 又沒有強制要求實作和宣告的檔名一定要有關連, 02/12 09:15
closer76: 那麼編譯器就得搜尋整個專案,才能找到定義的原始碼。增 02/12 09:15
closer76: 加編譯器實作成本。那為何不乾脆要求放在一起呢? 02/12 09:15
alan23273850: @closer76 如果不是 template 的話不也是要搜尋整 02/12 09:49
alan23273850: 個專案嗎? 02/12 09:49
closer76: 沒有 template 的話,至少可以先編成 obj,symbol table 02/12 13:11
closer76: 對 linker 來說也是比較容易處理的資料 02/12 13:11
closer76: C++ 原本是用 export 關鍵字來做這件事的,但因為太難做 02/12 13:19
closer76: ,所以後來被移出標準了。可以參考這裡: 02/12 13:19
closer76: 裡面有提到 C++20 有 module 功能,但我沒研究過 02/12 13:21
firejox: 有external template 可以避免重覆生成啊 02/12 13:28 @ 以上紅底標記7位,每人1000P(稅前)發送完成! by AutoGiveP 2.12 我想結案了,參考完 https://stackoverflow.com/q/495021/11550178 這篇文章之後, 核心觀念大概就是,template definition 和一般的 class definition 有所不同, 一般的 class 可以分成 .h 和 .cpp 是因為具體的定義可以直接生成 .obj 檔案;然而 template definition 在參數未給定之前是無法生成具體的 .obj 的,必須等到參數給定 之後才行,因此即使當下 .cpp 可以 #include .h 檔,但是在還不確定要針對哪些參數 類別去做實體化的理由之下,也無用武之地。 如果直接把 template 實作在 .h 檔,那其他人 #include 這個 .h 檔之後就可以直接 看到實作,那當他想要使用實體化過後的 class 的時候就可以直接實體化。如果維持 .h 和 .cpp 分離,那唯一的手法就是在該 .cpp 底下加上 template struct Class<int>; 這句話以實作出「此 .cpp 所定義的」每個 member function,副作用就是整個 project 就只能使用 Class<int>,不能使用其他的 Class<T>,只要是某個 T 想用到 A.cpp 實作 的 member function,就必須在 A.cpp 底下補上 template struct Class<T>,同時也會 強迫 A.cpp 的其他 member function 也順勢實體化。 由於我的 .cpp 還有他人協作必須 #include 其他的 header file,無法任意搬家進 .h 檔裡面,所以最後採取的作法就是把不會受到限制的函式實作再移到另外一個 .cpp 檔, 例如 A.cpp (含 print 實作) 末尾加上 template struct AUTOQ::Util::BinaryTree<int>; template struct AUTOQ::Util::BinaryTree<string>; 而 B.cpp (含 add 實作) 末尾只加上 template struct AUTOQ::Util::BinaryTree<int>; 如此一來問題就解決了,我不需要在特化 BinaryTree<string> 的時候再給出另一份新的 實作,可以直接使用原本的實作即可。 我大概猜得到原問題 (1) 的方法如果改加在 .h 檔應該會編譯成功,但是仍然不知為何 寫在 .cpp 無法有相同效果,是因為編譯器不支援部分實現嗎?只是這樣也不合理,那我 後來分成 A.cpp 和 B.cpp 的解法,不也是部分實現嗎? 註一:在我的中文用語認知內實現和特化有所不同,特化是指對於特定的參數提供另一種 函式的實作。 註二:@Fenikso 大大的解法我有試跑過,但它好像還是擋不住類似 template struct Foo<int>; template struct Foo<std::string>; 加在 main 前面的實現,還是會報 error if Foo contains some function that cannot be instantiated with std::string. 但是如果不加那兩行的話,也就不需要 requires 了。不知道如果要堅持這解法的話有沒有改進方案。 目前總共有 7 位鄉民留言,考量到個人財力,我想播 $7000 給各位鄉親,一人一千, 穴穴各位!
Fenikso: 不知道你怎麼改的 報error的寫法丟上來看看? 02/12 23:52
alan23273850: 感謝樓上大大持續追蹤,請先切換到 https://github 02/13 19:52
alan23273850: .com/alan23273850/AutoQ/commit/8ebd44aeeaa42a2e 02/13 19:52
alan23273850: 68ee80779147b84b17301e42 這個記錄點,按照 readm 02/13 19:52
alan23273850: e 去編譯會發現可以過,接著把此變化內的 aut_oper 02/13 19:52
alan23273850: ation.cc 最底部 4 個 Automata<Predicate> 實作的 02/13 19:52
alan23273850: 函式直接改成分號結尾,然後就會發現不能編譯了 :( 02/13 19:52
Fenikso: 啊因為你不能instantiate那些不該出現的function 02/14 14:21
Fenikso: https://bit.ly/3Xt3iN1 把所有不該給Predicate用的東西 02/14 14:24
Fenikso: 加上requires這樣就行了 02/14 14:24
alan23273850: 感謝 @Fenikso 大大幫我改程式碼,我有空再消化消化 02/14 19:01
alan23273850: 看懂再加碼 1000P 02/14 19:01
alan23273850: 所以那個 requires 裡面的三行是故意去呼叫看看那 02/14 19:51
alan23273850: 三個函式有沒有支援嗎?可是看起來好像 runtime 會 02/14 19:51
alan23273850: 實際去執行的程式碼。 02/14 19:51
Fenikso: 對看起來很像 runtime 但不是 XD 02/14 21:09
Fenikso: 是在 compile time 檢查 type T 有沒有支援這些操作 02/14 21:09 @ 以上紅底標記1位,每人1000P(稅前)發送完成! by AutoGiveP 2.12 ※ 編輯: alan23273850 (111.82.62.46 臺灣), 02/14/2023 21:41:22