[問題] 如何只特化參數類別的其中一個函式

看板C_and_CPP (C/C++)作者 (God of Computer Science)時間1年前 (2023/02/11 00:26), 1年前編輯推噓5(5052)
留言57則, 4人參與, 1年前最新討論串1/1
開發平台(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

02/11 00:27, 1年前 , 1F
程式碼我明天再補
02/11 00:27, 1F
LPH66: 繼承該特定 specialization 並 override 掉你要特化的函數 02/11 02:58

02/11 02:59, 1年前 , 2F
這樣可以嗎?
02/11 02:59, 2F

02/11 03:00, 1年前 , 3F
噢等等, 如果該函數沒有 virtual 那 override 抓不到
02/11 03:00, 3F

02/11 03:00, 1年前 , 4F
原程式碼呼叫被蓋掉的函數的狀況
02/11 03:00, 4F

02/11 03:02, 1年前 , 5F
不過如果你動得到模版原始碼的話, 加個 virtual 應該就行了
02/11 03:02, 5F
jack7775kimo: CRTP? 02/11 07:55

02/11 11:51, 1年前 , 6F
我補上程式碼了,這個問題對我來說很重要,如果獲得
02/11 11:51, 6F

02/11 11:51, 1年前 , 7F
解答的話我將奉送大量批幣!
02/11 11:51, 7F
nh60211as: 把實作放在header 02/11 12:04

02/11 12:07, 1年前 , 8F
@jack7775kimo 大那個關鍵字我剛剛查了一下好像很酷
02/11 12:07, 8F

02/11 12:07, 1年前 , 9F
但不確定能不能用在這裡
02/11 12:07, 9F

02/11 12:07, 1年前 , 10F
@nh60211as 大的解法我可能需要更具體的理由
02/11 12:07, 10F
Fenikso: 能動header的話就用concept或enable_if處理吧 02/11 13:57

02/11 13:57, 1年前 , 11F

02/11 14:18, 1年前 , 12F
template 其實會常見把實作寫在 header 裡的做法
02/11 14:18, 12F

02/11 14:19, 1年前 , 13F
理由是模版實現只在給定所有模版參數之後
02/11 14:19, 13F

02/11 14:19, 1年前 , 14F
我理解 @Fenikso 大大正面表列的作法,但是這樣會有
02/11 14:19, 14F

02/11 14:19, 1年前 , 15F
除非像你這樣特別去引用一個模版把它特化出來
02/11 14:19, 15F

02/11 14:19, 1年前 , 16F
一堆 function 要加上 requires 很不方便,更重要的
02/11 14:19, 16F

02/11 14:20, 1年前 , 17F
不然你是無法對別的 TU 裡引用的模版去產生程式碼的
02/11 14:20, 17F

02/11 14:20, 1年前 , 18F
是,我不知道為何內文 (1) 的作法編譯器會不通過。
02/11 14:20, 18F

02/11 14:21, 1年前 , 19F
把實作寫在標頭就把很多決定模版的地方延後到使用處生成
02/11 14:21, 19F

02/11 14:25, 1年前 , 20F
哦哦哦 但是我好像漸漸對 @Fenikso 大大的答案有點
02/11 14:25, 20F

02/11 14:26, 1年前 , 21F
感覺了,只要實作的地方先加註 prerequisites 確保
02/11 14:26, 21F

02/11 14:26, 1年前 , 22F
實作完畢,才開始實現我這個函式,好像就能避免掉我
02/11 14:26, 22F

02/11 14:26, 1年前 , 23F
那個問題,我禮拜一有空會試試看,如果 OK 的話就
02/11 14:26, 23F

02/11 14:27, 1年前 , 24F
奉送 @Fenikso 一個大禮!
02/11 14:27, 24F

02/11 14:28, 1年前 , 25F
也謝謝 @LPH66 大大的解說,兩個我都會試試看。
02/11 14:28, 25F
wulouise: 為甚麼你要寫在source file內感覺才是癥結點 02/12 00:17

02/12 00:36, 1年前 , 26F
一般不是都鼓勵分成兩個檔案嗎?不然理由是什麼呢
02/12 00:36, 26F
closer76: 其實你去看大部分使用template 的函式庫,都是直接把實 02/12 09:03

02/12 09:03, 1年前 , 27F
作寫在 header files 裡的。原因是大部分的編譯器都不支
02/12 09:03, 27F

02/12 09:03, 1年前 , 28F
援有 template 的類別、函式內容另外定義。
02/12 09:03, 28F

02/12 09:15, 1年前 , 29F
其實你可以用編譯器的角度想想:template 其實就是編譯
02/12 09:15, 29F

02/12 09:15, 1年前 , 30F
器要幫你特化,所以編譯器需要知道 template 的原始碼。
02/12 09:15, 30F

02/12 09:15, 1年前 , 31F
而 C++ 又沒有強制要求實作和宣告的檔名一定要有關連,
02/12 09:15, 31F

02/12 09:15, 1年前 , 32F
那麼編譯器就得搜尋整個專案,才能找到定義的原始碼。增
02/12 09:15, 32F

02/12 09:15, 1年前 , 33F
加編譯器實作成本。那為何不乾脆要求放在一起呢?
02/12 09:15, 33F

02/12 09:49, 1年前 , 34F
@closer76 如果不是 template 的話不也是要搜尋整
02/12 09:49, 34F

02/12 09:49, 1年前 , 35F
個專案嗎?
02/12 09:49, 35F

02/12 13:11, 1年前 , 36F
沒有 template 的話,至少可以先編成 obj,symbol table
02/12 13:11, 36F

02/12 13:11, 1年前 , 37F
對 linker 來說也是比較容易處理的資料
02/12 13:11, 37F

02/12 13:19, 1年前 , 38F
C++ 原本是用 export 關鍵字來做這件事的,但因為太難做
02/12 13:19, 38F

02/12 13:19, 1年前 , 39F
,所以後來被移出標準了。可以參考這裡:
02/12 13:19, 39F

02/12 13:20, 1年前 , 40F

02/12 13:21, 1年前 , 41F
裡面有提到 C++20 有 module 功能,但我沒研究過
02/12 13:21, 41F
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 給各位鄉親,一人一千, 穴穴各位!

02/12 23:52, 1年前 , 42F
不知道你怎麼改的 報error的寫法丟上來看看?
02/12 23:52, 42F

02/13 19:52, 1年前 , 43F
感謝樓上大大持續追蹤,請先切換到 https://github
02/13 19:52, 43F

02/13 19:52, 1年前 , 44F
.com/alan23273850/AutoQ/commit/8ebd44aeeaa42a2e
02/13 19:52, 44F

02/13 19:52, 1年前 , 45F
68ee80779147b84b17301e42 這個記錄點,按照 readm
02/13 19:52, 45F

02/13 19:52, 1年前 , 46F
e 去編譯會發現可以過,接著把此變化內的 aut_oper
02/13 19:52, 46F

02/13 19:52, 1年前 , 47F
ation.cc 最底部 4 個 Automata<Predicate> 實作的
02/13 19:52, 47F

02/13 19:52, 1年前 , 48F
函式直接改成分號結尾,然後就會發現不能編譯了 :(
02/13 19:52, 48F

02/14 14:21, 1年前 , 49F
啊因為你不能instantiate那些不該出現的function
02/14 14:21, 49F

02/14 14:24, 1年前 , 50F
https://bit.ly/3Xt3iN1 把所有不該給Predicate用的東西
02/14 14:24, 50F

02/14 14:24, 1年前 , 51F
加上requires這樣就行了
02/14 14:24, 51F

02/14 19:01, 1年前 , 52F
感謝 @Fenikso 大大幫我改程式碼,我有空再消化消化
02/14 19:01, 52F

02/14 19:01, 1年前 , 53F
看懂再加碼 1000P
02/14 19:01, 53F

02/14 19:51, 1年前 , 54F
所以那個 requires 裡面的三行是故意去呼叫看看那
02/14 19:51, 54F

02/14 19:51, 1年前 , 55F
三個函式有沒有支援嗎?可是看起來好像 runtime 會
02/14 19:51, 55F

02/14 19:51, 1年前 , 56F
實際去執行的程式碼。
02/14 19:51, 56F

02/14 21:09, 1年前 , 57F
對看起來很像 runtime 但不是 XD
02/14 21:09, 57F
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
文章代碼(AID): #1Zvd1FjJ (C_and_CPP)
文章代碼(AID): #1Zvd1FjJ (C_and_CPP)