C++模板——函數(shù)模板
1.1 定義函數(shù)模板
template<typename T>
T max(T a,T b) {
return b < a ? a : b;
}
1.2 使用函數(shù)模板
std::cout << max(7,42) << std::endl;
std::cout << max(1.1,2.2) << std::endl;
std::cout << max("math","mathematics") << std::endl;
模板不是被編譯成可以處理任何類型的單個(gè)函數(shù)。相反,編譯器會(huì)針對(duì)每一個(gè)使用該模板的類型生成對(duì)應(yīng)的函數(shù)。例如,max(7,42)
的調(diào)用在語(yǔ)義上相當(dāng)于調(diào)用了:
int max(int a,int b) {
return b < a ? a : b;
}
double、string同理。
將模板參數(shù)替換成具體參數(shù)類型的過(guò)程叫做instantiation
,這個(gè)過(guò)程會(huì)產(chǎn)生一個(gè)instance of template
。
1.3 兩階段翻譯 Two-Phase Translation
如果某一特定參數(shù)類型不支持模板內(nèi)的操作,那么編譯階段會(huì)報(bào)錯(cuò),例如:
std::complex<float> c1,c2; ? ? ? ?//不支持 max中的 < 操作,編譯階段會(huì)報(bào)錯(cuò)
...
max(c1,c2);
模板會(huì)分成兩個(gè)階段進(jìn)行”編譯“:
在不進(jìn)行模板
instantiation
的definition time
階段,此時(shí)會(huì)忽略模板參數(shù),檢查如下方面:語(yǔ)法錯(cuò)誤,包括缺失分號(hào)。
使用未定義參數(shù)。
如果static assertion不依賴模板參數(shù),會(huì)檢查是否通過(guò)static assertion.
在
instantiation
階段,會(huì)再次檢查模板里所有代碼的正確性,尤其是那些依賴模板參數(shù)的部分。
例如:
template<typename T>
void foo(T t) {
undeclared(); ? ? ? ? // first-phase compile-time error if undeclared() unknown
undeclared(t); ? ? ? // second-phase compile-time error if undeclared(T) unknown
static_assert(sizeof(int) > 10,"int too small"); ? ? ?// first-phase compile-time error
static_assert(sizeof(T) > 10, "T too small"); ? ? ? ?// second-phase compile-time error
}
1.3.1 模板的編譯和鏈接問(wèn)題
大多數(shù)人會(huì)按照如下方式組織非模板代碼:
將類或者其他類型聲明放在頭文件(.hpp、.H、.h、.hh、.hxx)中。
將函數(shù)定義等放到一個(gè)單獨(dú)的編譯單元文件中(.cpp、.C、.c、.cc、.cxx)。
但是這種組織方式在包含模板的代碼中卻行不通,例如:
頭文件:
// myfirst.hpp
// declaration of template
template<typename T>
void printTypeof (T const&);
定義函數(shù)模板的文件:
// myfirst.cpp
// implementation/definition of template
template<typename T>
void printTypeof (T const& x) {
std::cout << typeid(x).name() << '\n';
}
在另一個(gè)文件中使用該模板:
// myfirstmain.cpp
#include "myfirst.hpp"
// use of the template
int main() {
double ice = 3.0;
printTypeof(ice); // call function template for type double
}
在c/c++中,當(dāng)編譯階段發(fā)現(xiàn)一個(gè)符號(hào)(printTypeof)沒(méi)有定義只有聲明時(shí),編譯器會(huì)假設(shè)它的定義在其他文件中,所以編譯器會(huì)留一個(gè)”坑“給鏈接器linker,讓它去填充真正的符號(hào)地址。
但是上面說(shuō)過(guò),模板是比較特殊的,需要在編譯階段進(jìn)行instantiation
,即需要進(jìn)行模板參數(shù)類型推斷,實(shí)例化模板,當(dāng)然也就需要知道函數(shù)的定義。但是由于上面兩個(gè)cpp文件都是單獨(dú)的編譯單元文件,所以當(dāng)編譯器編譯myfirstmain.cpp
時(shí),它沒(méi)有找到模板的定義,自然也就沒(méi)有instantiation
。
解決辦法就是我們把模板的聲明和定義都放在一個(gè)頭文件。大家可以看一下自己環(huán)境下的vector等STL源文件,就是把類的聲明和定義都放在了一個(gè)文件中。
1.4 多模板參數(shù)
template<typename T1, typename T2>
T1 max (T1 a, T2 b) {
return b < a ? a : b;
}
...
auto m = max(4, 7.2); ? ? ? // 注意:返回類型是第一個(gè)模板參數(shù)T1 的類型
但是問(wèn)題正如注釋中說(shuō)的,max的返回值類型總是T1。如果我們調(diào)用max(42, 66.66)
,返回值則是66。
一般有三個(gè)方法解決這個(gè)問(wèn)題:
引入額外模板參數(shù)作為返回值類型
讓編譯器自己找出返回值類型
將返回值聲明為兩個(gè)模板參數(shù)的公共類型,比如int和float,公共類型就是float
1.4.1 引入額外模板參數(shù)作為返回值類型
在函數(shù)模板的參數(shù)類型推導(dǎo)過(guò)程中,一般我們不用顯式指定模板參數(shù)類型。但是當(dāng)模板參數(shù)不能根據(jù)傳遞的參數(shù)推導(dǎo)出來(lái)時(shí),我們就需要顯式的指定模板參數(shù)類型。
template<typename T1, typename T2, typename RT>
RT max(T1 a, T2 b);
RT是不能根據(jù)函數(shù)的參數(shù)列表推導(dǎo)出來(lái)的,所以我們需要顯式的指定:
max<int, double, double>(4, 7.2);
或者我們改變模板參數(shù)列表順序,這種情況只需顯式的指定一個(gè)參數(shù)類型即可:
template<typename RT typename T1, typename T2> ? ? ?//RT變?yōu)榈谝粋€(gè)模板參數(shù)
RT max(T1 a, T2 b);
...
max<double>(4, 7.2);
1.4.2 讓編譯器自己找出返回值類型
在C++11中,我們可以利用auto和trailing return type來(lái)讓編譯器找出返回值類型:
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b < a ? a : b) {
return b < a ? a : b;
}
decltype后面的文章會(huì)講到,這里只需知道它可以獲取到表達(dá)式的類型。
我們可以寫的更簡(jiǎn)單點(diǎn):
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(true ? a : b) { ? ? ?// true ? a : b
return b < a ? a : b;
}
關(guān)于?:返回值規(guī)則可以參考這個(gè):Conditional Operator: ? :
看到true ? a : b
不要奇怪為什么是true,這里的重點(diǎn)不是計(jì)算返回值,而是得到返回值類型。
在C++14中,我們可以省略trailing return type:
template<typename T1, typename T2>
auto max (T1 a, T2 b) {
return b < a ? a : b;
}
1.4.3 將返回值聲明為兩個(gè)模板參數(shù)的公共類型
c++11新特性std::common_type
可以產(chǎn)生幾個(gè)不同類型的共同類型,其實(shí)核心意思跟上面說(shuō)的差不多:
template <typename T1, typename T2>
typename std::common_type<T1, T2>::type max(T1 a, T2 b) {
return b < a ? a : b;
}
在c++14中,可以更簡(jiǎn)單的寫:
template <typename T1, typename T2>
std::common_type_t<T1, T2> max(T1 a, T2 b) {
return b < a ? a : b;
}
這里使用_t
后綴讓我們不用寫typename
和::type
。類似的還有_v,這個(gè)在c++14的type traits
里很常見(jiàn)。
1.5 默認(rèn)模板參數(shù)
這個(gè)很像函數(shù)的默認(rèn)參數(shù),直接看例子:
template <typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>
RT max(T1 a, T2 b) {
return b < a ? a : b;
}
auto a = max(4, 7.2);
auto b = max<double,int,long double>(7.2, 4);
正如第二個(gè)用法,如果我們想顯示的指明RT的類型,必須顯示的指出全部三個(gè)參數(shù)類型。但是與函數(shù)默認(rèn)參數(shù)不同的是,我們可以將默認(rèn)參數(shù)放到第一個(gè)位置:
template <typename RT = long, typename T1, typename T2>
RT max(T1 a, T2 b) {
return b < a ? a : b;
}
int i;
long l;
…
max(i, l); ? ? ? ? ? ? ? ? ? ? // 返回值類型是long (RT 的默認(rèn)值)
max<int>(4, 42); ? ? ?//返回int,因?yàn)槠浔伙@式指定
1.6 重載函數(shù)模板
這個(gè)跟普通函數(shù)重載也類似:
// maximum of two int values:
int max(int a, int b) {
return b < a ? a : b;
}
// maximum of two values of any type:
template <typename T>
T max(T a, T b) {
return b < a ? a : b;
}
int main() {
max(7, 42); ? ? ? ? // calls the nontemplate for two ints
max(7.0, 42.0); ? ? // calls max<double> (by argument deduction)
max('a', 'b'); ? ? ?// calls max<char> (by argument deduction)
max<>(7, 42); ? ? ? // calls max<int> (by argument deduction)
max<double>(7, 42); // calls max<double> (no argument deduction)
max('a', 42.7); ? ? // calls the nontemplate for two ints
}
這里需要解釋下最后一個(gè)max('a', 42.7)
。因?yàn)樵谀0鍏?shù)類型推導(dǎo)過(guò)程中不允許類型自動(dòng)轉(zhuǎn)換,但是調(diào)用普通函數(shù)是允許的,所以這個(gè)會(huì)調(diào)用非模板函數(shù)。
ps. 由于函數(shù)模板重載,所以函數(shù)模板并不像類模板一樣可以進(jìn)行偏特化。
還有兩點(diǎn)關(guān)于重載的基本原則需要了解一下:
1.6.1 重載時(shí)最好不要隨便改變模板參數(shù)個(gè)數(shù),最好可以顯示的指定模板參數(shù)類型
下面是段有問(wèn)題的代碼:
// maximum of two values of any type (call-by-reference)
template <typename T> T const &max(T const &a, T const &b) {
return b < a ? a : b;
}
// maximum of two C-strings (call-by-value)
char const *max(char const *a, char const *b) {
return std::strcmp(b, a) < 0 ? a : b;
}
// maximum of three values of any type (call-by-reference)
template <typename T> T const &max(T const &a, T const &b, T const &c) {
return max(max(a, b), c); ? ? ? ? ? // error if max(a,b) uses call-by-value
}
int main() {
auto m1 = max(7, 42, 68); ? ? ? ? // OK
char const *s1 = "frederic";
char const *s2 = "anica";
char const *s3 = "lucas";
auto m2 = max(s1, s2, s3); ? ? ? ? // run-time ERROR
}
問(wèn)題出現(xiàn)在return max (max(a,b), c);
,因?yàn)?code>char const *max(char const *a, char const *b)的參數(shù)是按值傳遞,max(a,b)
會(huì)產(chǎn)生一個(gè)指向已經(jīng)銷毀的棧幀地址,這會(huì)導(dǎo)致未定義行為。
1.6.2 確保所有被重載的函數(shù)模板在使用時(shí)已經(jīng)被聲明定義
// maximum of two values of any type:
template <typename T>
T max(T a, T b) {
std::cout << "max<T>()\n";
return b < a ? a : b;
}
// maximum of three values of any type:
template <typename T>
T max(T a, T b, T c) {
return max(max(a, b), c);
}
// maximum of two int values:
int max(int a, int b) {
std::cout << "max(int,int) \n";
return b < a ? a : b;
}
int main() {
max(47, 11, 33); ? ?// max<T>()
}
這點(diǎn)很好理解。
鏈接:https://www.dianjilingqu.com/612129.html