C++型別推導(dǎo)1——模板推導(dǎo)

?? 自C++98開始,C++就具備了類型自動推導(dǎo)的能力。在11版本發(fā)布之前,C++只有一種類型推導(dǎo)方法,即模板類型推導(dǎo)。在C++11中額外增加了2種類型推導(dǎo)方法——使用auto、decltype關(guān)鍵字。

?? “C++型別(類型)推導(dǎo)”系列文章會結(jié)合代碼和實(shí)際編譯環(huán)境的輸出來分別說明以上三種情況的型別推導(dǎo)規(guī)則,由于內(nèi)容,篇幅有些多,準(zhǔn)備分為3篇文章分別對C++的3種型別推導(dǎo)進(jìn)行記錄,本文先完整介紹template對型別的推導(dǎo)規(guī)則。

??書上將測試推導(dǎo)的工具和方法放在了最后講,我這里提到最開始來介紹以方便大家測試:
?? 作者首先提倡讀者應(yīng)該在腦海中牢記這些推導(dǎo)規(guī)則,其次也推薦了利用IDE編寫讓編譯器報(bào)錯的一些代碼等方式來查看類型推導(dǎo)結(jié)果,如:

template<typename T>
class TD;//只聲明一個模板類,不定義它,一旦該類被使用,編譯器就會報(bào)錯。
int main(){
  const int theAnswer = 42;
  auto x = theAnswer;
  auto y = &theAnswer;
  TD<decltype(x)> xType;//error C2079: “xType”使用未定義的 class“TD<int>”
  TD<decltype(y)> yType;//error C2079: “yType”使用未定義的 class“TD<const int *>”   
  return 0;
}

??從編譯器的報(bào)錯中,我們能清楚地看到類型推導(dǎo)的結(jié)果。
下面所有推導(dǎo)過程都以以下代碼為大致模型:

template <typename T>
void f(ParamType param);
……
f(expr);

一、模板型別推導(dǎo)

這種方式分為三種情況:
1.ParamType是指針或者引用類型,但不是萬能引用。
2.ParamType是一個萬能引用。
3.ParamType既不是指針也不是引用。

注意,ParamType是形參,而不是指的實(shí)參

??這里提到的“萬能引用”書中沒有立即細(xì)致說明,這里我簡單介紹一下:
??所謂“萬能引用”,就是既不是左值引用也不是右值引用的引用,并且它最終既可以做左值引用也可以做右值引用。形如:T&&,形狀看起來和右值引用沒啥區(qū)別,但有一個簡單的區(qū)分:長這個樣子的,只要涉及型別推導(dǎo),那么它就是一個萬能引用,否則就是右值引用,舉例:

    void f(Widget&& param);//param是右值引用,因?yàn)樗念愋鸵呀?jīng)確定,不涉及型別推導(dǎo)

    Widget&& var1 = Widget();//同上

    auto&& var2 = var1;//萬能引用

    template<typename T>
    void f(std::vector<T>&& param);//右值引用
    
    template<typename T>
    void f(T&& param);//萬能引用

??萬能引用的作用就是當(dāng)你傳入左值,那么它最終就是左值引用,傳入右值,那么它就是右值引用。


??接著第一種情況說起,如果ParamType(形參)是一個指針或者引用(非萬能引用),那么模板型別推導(dǎo)的最終結(jié)果會忽略掉實(shí)參的引用部分(如果有)。當(dāng)param是一個引用,如:

   template<typename T>
   void f(T& param);

   int x = 10;
   const int cx = x;
   const    int& rx = x;

  f(x);//param為int&
  f(cx);//param為cosnt int&
  f(rx);//param為cosnt int&(實(shí)參的&被忽略了)
  //以上的形參是左值引用,當(dāng)改成右值引用時,情況一樣

??如果把param加一個const修飾: const T& 如:

   template<typename T>
   void f(const T& param);//加了const

   int x = 10;//同上
   const int cx = x;//同上
   const    int& rx = x;//同上

  f(x);//param為const int&
  f(cx);//param為cosnt int&
  f(rx);//param為cosnt int&(實(shí)參的&被忽略了) 同上
  //以上的形參是左值引用,當(dāng)改成右值引用時,情況一樣

??結(jié)果平淡無奇,而當(dāng)param是一個指針時,和引用的推導(dǎo)是一模一樣的:

   template<typename T>
   void f(T* param);//param是一個指針了

   int x = 10;
   const int cx = x;
   const    int* px = &x;

  f(x);//param為int*
  f(px);//param為cosnt int*

??沒有什么特別的地方……

??第二種情況,當(dāng)ParamType是萬能引用T&&

   template<typename T>
   void f(T&& param);//param現(xiàn)在是個萬能引用

   int x = 10; //同前
   const int cx = x;//同前
   const    int& rx = x;//同前

  f(x);//因?yàn)閤是左值,所以param也為左值,int&
  f(cx);//因?yàn)閤是左值,所以param也為左值,cosnt int&
  f(rx);/因?yàn)閤是左值,所以param也為左值 ,cosnt int&(實(shí)參的&被忽略了)

  f(10);//因?yàn)?0是右值,所以param也為右值,int&&

??第三種情況,當(dāng)ParamType既不是引用也不是指針時(pass by value)
??首先,模板型別推導(dǎo)會像之前的情況一樣,忽略掉實(shí)參的引用部分。
??其次,按值傳遞意味著param對象將是實(shí)參的一個副本,由于對副本的修改并不會影響原有對象,所以如果原有對象(實(shí)參)具有const、valatile的修飾,也會一并忽略掉。

  template<typename T>
  void f(T param);//param是實(shí)參的一個副本

  int x = 10; //同前
  const int cx = x;//同前
  const    int& rx = x;//同前

 f(x);//param是int
 f(cx);//param是int,忽略掉了const
 f(rx);//param是int,忽略掉了const和&

??這里有一個復(fù)雜一點(diǎn),但并不會產(chǎn)生違背以上規(guī)則的例子:
??如果實(shí)參是一個指向const的const指針,如:

  template<typename T>
  void f(T param);//param是實(shí)參的一個副本

  int x = 10; //同前
  const int* const px = &x;

 f(px);//param是const int*

?? px的第二個const 是指 px本身不允許再發(fā)生任何更改,即不能再指向其它任何變量。它的第一個const是指,它指向的這個變量不可以再被修改。由于是按值傳遞,根據(jù)規(guī)則,型別的推導(dǎo)會忽略掉實(shí)參的const屬性,而實(shí)參的類型是一個 const的指向const int的指針,所以px自己的const(右邊的)會被忽略,于是param的類型變成了 const int。

??這里要補(bǔ)充一下另外的特殊情況,那就是當(dāng)實(shí)參類型為數(shù)組和函數(shù)的時候,需要注意:
?? C++中,數(shù)組名可以退化成指針,函數(shù)也一樣,一個函數(shù)類型是可以退化成函數(shù)指針的。如下:

 template<typename T>
 void f1(T param);
 template <typename T>
 void f2(T& param);

 int arr = {0,1,2,3};
 void someFunc(int,double);
 
 f1(arr);//arr會退化成指針,即param的類型為int*
 f2(arr);//這里很有意思,param會被推導(dǎo)為 int (&)[4]
 //和上面的情況相比好處是param包含了數(shù)組的元素?cái)?shù)量信息,可以獲取到數(shù)組的成員數(shù)。
 f1(someFunc);//param類型為函數(shù)指針 void (*)(int,double)
 f2(someFunc);param類型為函數(shù)引用 void (&)(int,double)

??到這里,template的型別推導(dǎo)規(guī)則就說完了,下一篇文章將會介紹auto的型別推導(dǎo)規(guī)則。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容