模板是C++中很重要的一個特性,利用模板可以編寫出類型無關的通用代碼,極大的減少了代碼量,提升工作效率。C++中包含類模板、函數模板,對於需要特殊處理的類型,可以通過特化的方式來實現特定類型的特殊操作。
最近工作中,需要處理CONT<TYPE>這種復合類型和T這種自定義類型的模板特化,因為CONT類型有五種左右需要特殊處理,其余的可以用默認處理函數,TYPE的具體類型有上千種,但是TYPE類型不涉及不同的操作。
方案一:采用兩個模板參數
template <typename TYPE, template<typename> typename CONT> void func(const CONT<TYPE> &v) {} //或 template <typename T> void func(const T &v) {}
問題:由於C++不支持模板函數偏特化,實際特化的時候會退化的第二個的情形,即需要手動編寫幾千個函數來實現特化,這種方法不可接受。
方案二:采用偏特化類模板的第二個模板參數
template<typename TYPE, template<typename> typename CONT> struct my_class { my_class() = delete; static void func(const CONT<TYPE> &v) {} }; //將CONT偏特化為vector template <typename TYPE> struct my_class<TYPE, vector> { my_class() = delete; static void func(const vector<TYPE> &v) {} };
問題:首先,這種形式的偏特化可以實現復合類型的需求,但是對於用戶自定義類型無法兼容
方案三:利用SFINAE控制模板函數實例化實現“偏特化” (示例代碼部分使用C++17語法)
//“偏特化”vector容器類型的模板函數 template <typename TYPE, template<typename> typename CONT> void func(const CONT<TYPE> &v, std::enable_if_t<std::is_fundamental<TYPE>::value && std::is_same_v<vector<int>, CONT<int>>> * = nullptr) {} //如果使用C++11版本的gcc時,偏特化vector等標准容器類型的模板函數需要使用如下形式 template <typename T, template<class, class...> class C, class... Args> void var_func(const C<T, Args...>& v) { std::cout << "var_func type" << std::endl; } //“偏特化”用戶自定義非容器類型的函數 template <typename TYPE, template<typename> typename CONT> void func(const TYPE &v, std::enable_if_t<!std::is_fundamental<TYPE>::value> * = nullptr) {} //默認的復合類型模板函數 template <typename TYPE, template<typename> typename CONT> void func(const CONT<TYPE> &v, std::enable_if_t<std::is_fundamental<TYPE>::value && !std::is_same_v<vector<int>, CONT<int>> && !std::is_same_v<deque<int>, CONT<int>> && !std::is_same_v<list<int>, CONT<int>>> * = nullptr) {} //對於函數體內的auto類型的變量(由CONT和TYPE組合成的復合類型),可以通過以下手段判斷其類型 using T = std::decay_t<decltype(variable)>; if constexpr (std::is_same_v<vector<type>, T>) func<type, vector>(variable); else if constexpr (std::is_same_v<my_type, T>) func<my_type, deque>(variable); ....
結論:最后采用了這種方案,結合了std::enable_if_t, std::is_same_v, std::is_base_of 等語法來實現模板函數的條件來實現了函數模板的“偏特化”
Tips:
- 函數模板的定義和聲明必須都放到頭文件中,因為編譯器在編譯階段需要依據頭文件中模板函數的定義來實現模板的實例化
- 不建議將函數模板編譯成靜(動)態庫,然后源文件只包含模板函數聲明的頭文件,這樣在鏈接的時候提示undefined 的類似錯誤,因為函數庫只能導出實例化的函數,對於沒有實例化模板函數,就會出現undefined的錯誤(除非在函數庫中,將所有可能的實例化類型都實例化一遍)。建議,對模板的使用通過頭文件的方式。
- 類模板如果有默認模板參數,那么只能在聲明或者定義其中任何一個位置設置默認模板參數,不能兩個位置都設置。