函數模板非凡化 非成員函數模板也可以進行非凡化。在有些情況下,您可以充分利用有關類型的一些專門知識,來編寫比從模板實例化的函數更高效的函數。在其他一些情況下,常規模板的定義對某種類型而言根本就是錯誤的。
![]()
例如,假設您擁有函數模板 max 的定義: template <class T>T max( T t1, T t2 ) {return ( t1 > t2 ? t1 :t2 );} 假如用 System::String 類型的模板參數實例化該函數模板,所生成的實例就無法編譯,因為正如您在前面所看到的,String 類不支持小于 (<) 或大于 (>) 運算符。圖 4 中的代碼說明了如何非凡化函數模板。(同樣必須先聲明常規函數模板才能進行非凡化。) 假如可以從函數參數推斷出模板參數,則可以從顯式非凡化聲明中對實際類型參數省略函數模板名稱的限定 max<String^>。例如,編譯器可以在下面的 max 模板非凡化中推斷出 T 綁定到 String,因此在這種情況下,為方便起見,該語言答應使用下面的簡寫表示法: // 沒問題:從參數類型推斷出 T 綁定到 Stringtemplate<> String^ max( String^, String^ );引入此顯式非凡化后,下面的調用就會解析為這個非凡化的實例: void foo( String^ s1, String^ s2 ) {String^ maxString = max( s1, s2 ); // ...}
假如兩個參數的類型均為 String,常規函數模板不會擴展。這正是我們所需要的行為。只要提供了顯式函數模板非凡化,就必須始終指定 template<> 和函數參數列表。例如,max 的下面兩個聲明不合法,并且在編譯時會被標記為: // 錯誤:無效的非凡化聲明// 缺少 template<>String^ max<String^>( String^, String^ );// 缺少函數參數列表template<> String^ max<String^>; 有一種情況,省略函數模板非凡化的 template<> 部分不是錯誤。即,在您聲明的普通函數帶有與模板實例化相匹配的返回類型和參數列表的情況下: // 常規模板定義template <class T>T max( T t1, T t2 ) { /* ... */ }// 沒問題:普通函數聲明!String^ max( String^, String^ ); 毫無疑問,您經常會感到很無奈,并認為 C++ 真是太難理解了。您可能想知道,究竟為什么所有人都希望聲明與模板實例化相匹配的普通函數,而不希望聲明顯式非凡化。那么,請看下面的示例,事情并不是完全按照您喜歡的方式進行的: void foo( String^ s1, String^ s2 ) {// 能否解析非凡化的實例?String^ maxString = max( "muffy", s2 ); // ... }在 C++/CLI 下,對于重載解決方案,字符串文字的類型既是 const char[n] [其中
n 是文字的長度加一(用于終止空字符)],又是 System::String。這意味著,給定一組函數 void f( System::String^ ); // (1)void f( const char* ); // (2)void f( std::string ); // (3)如下所示的調用 // 在 C++/CLI 下解析為 (1)f( "bud, not buddy" ); 與 (1) 完全匹配,而在 ISO-C++ 下,解析結果會是 (2)。因此,問題就是,對于函數模板的類型推斷而言,字符串文字是否還是被當作 System::String 進行處理?簡言之,答案是“不”。(具體的答案將是我下一期專欄的主題,該專欄將具體介紹函數模板。)因此,不選擇 max 的非凡化 String 實例,下面對 max 的調用 String^ maxString = max( "muffy", s2 ); // 錯誤在編譯時會失敗,因為 max 的定義要求兩個參數的類型均為 T: template <class T> T max( T t1, T t2 );那您能做些什么呢?像在下面的重新聲明中一樣,將模板改為帶有兩個參數的實例 template <class T1,class T2> ??? max( T1 t1, T2 t2 ); 使我們能夠編譯帶有 muffy 和 s2 的 max 的調用,但會因大于 (>) 運算符而斷開;并且指定要返回的參數類型。 我想做的就是始終將字符串文字強制轉換為 String 類型,這也是拯救普通函數的方法。 假如在推斷模板參數時使用了某個參數,那么只有一組有限的類型轉換可用于將函數模板實例化的參數轉換為相應的函數參數類型。還有一種情況是顯式非凡化函數模板。正如您所看到的,從字符串文字到 System::String 的轉換不屬于上述情況。 在存在有害字符串文字的情況下,顯式非凡化無助于避免對類型轉換的限制。假如您希望不僅答應使用一組有限的類型轉換,則必須定義普通函數而不是函數模板非凡化。這就是 C++ 答應重載非模板函數和模板函數的原因。進入討論組討論。
我基本上已經講完了,不過還有最后一點需要說明。創建一組您在圖 5 中看到的 max 函數意味著什么?您知道調用 max( 10, 20 );始終會解析為常規模板定義,并將 T 推斷為 int。同樣,您現在還知道調用
![]()
max( "muffy", s2 );max( s2, "muffy" );始終會解析為普通函數實例(其中文字字符串轉換為 System::String),但是有一個問題,調用 max( s2, s2 );會解析為三個 max 函數中的哪一個?要回答此問題,我們要查看解析重載函數的過程。
重載函數的解析過程
解析重載函數的第一步是建立候選函數集。候選函數集包含與被調用的函數同名并且在調用時能夠看到其聲明的函數。 第一個可見函數是非模板實例。我將該函數添加到候選列表中。那么函數模板呢?在能夠看到函數模板時,假如使用函數調用參數可以實例化函數,則該模板的實例化被視為候選函數。在我的示例中,函數參數為 s2,其類型為 String。模板參數推斷將 String 綁定到 T,因此模板實例化 max(String^,String^) 將添加到候選函數集中。 只有在模板參數推斷成功時,函數模板實例化才會進入候選函數集。但是,假如模板參數推斷失敗,不會出現錯誤;即,函數實例化沒有添加到候選函數集中。 假如模板參數推斷成功,但是模板是為推斷出的模板參數顯式非凡化的(正如我的示例一樣),會怎么樣呢?結果是,顯式模板非凡化(而不是通過常規模板定義實例化的函數)將進入候選函數。因此,此調用有兩個候選函數:非凡化的模板實例化和非模板實例。 // 候選函數// 非凡化的模板...template<> String^ max<String^>( String^ s1, String^ s2 );// 非模板實例String^ max( String^, String^ ); 解析重載函數的下一步是從候選函數集中選擇可行函數集。對于要限定為可行函數的候選函數,必須存在類型轉換,將每個實際參數類型轉換為相應的形式參數類型。在該示例中,兩個候選函數都是可行的。 解析重載函數的最后一步是,對參數所應用的類型轉換進行分級,以選擇最好的可行函數。例如,兩個函數看起來都很好。既然兩個函數都可行,那么這是否應該被視為不明確的調用? 實際上,調用是明確的:將調用非模板 max,因為它優先于模板實例化。原因是,在某種程度上,顯式實現的函數比通過常規模板創建的實例更為實用。 令人吃驚的是,在解決有害字符串文字的情況中,我已經徹底消除了調用以前的 String 非凡化的可能性,因此我可以消除這個問題。我只需要常規模板聲明以及重載的非模板實例: // 支持 String 的最終重載集template <class T>T max( T t1, T t2 ) { /* ... */ }String^ max( String^, String^ ); 這不一定會很復雜,但有一點是肯定的 - 在語言集成和靈活性方面,它遠遠地超過了公共語言運行時 (CLR) 泛型功能可以支持的范圍。 模板非凡化是 C++ 模板設計的基礎。它提供了最好的性能,克服了對單個或系列類類型的限制,具有靈活的設計模式,并且在實際代碼中已證實其巨大價值。在下一期專欄中,我將深入分析 C++/CLI 對模板函數和常規函數的支持。
請將您的疑問和意見通過 purecpp@microsoft.com 發送給 Stanley。Stanley B. L
ippman 是 Microsoft 公司 Visual C++ 團隊的體系結構設計師。他從 1984 年開始在 Bell 實驗室與 C++ 的設計者 Bjarne Stroustrup 一起研究 C++。此后,他在 Disney 和 DreamWorks 制作過動畫,還擔任過 JPL 的高級顧問和
Fantasia 2000 的軟件技術主管。
轉到原英文頁面進入討論組討論。