第1課 理解函數模板的類型推導


第1課 理解模板類型推導

一、函數模板

(一)  函數模板及調用形式

template<typename T>

void f(ParamType param); //注意這里是ParamType而不是T。這兩者可能不一樣!

f(expr); //調用形式,以實參expr調用f

(二)討論:

    ①T和ParamType的類型往往不一樣。因為ParamType常包含一些修飾詞,如const或引用符號等限定詞。

    ②T的類型,不僅僅依賴於實參expr的類型,還依賴於ParamType的類型

    ③ParamType的形式可分為三種情況:A. ParamType是個指針或引用類型(非萬能引用)。B. ParamType是一個萬能引用。C. ParamType既非指針也非引用。

二、函數模板的推導規則

(一)規則1:ParamType是個指針或引用(但非萬能引用),即T*或T&等

 1、兩條規則:

    ①若expr是個引用類型,先將其引用忽略

    ②然后對expr的類型和ParamType的類型進行模式匹配,來決定T的類型。

   2、注意事項:

    ①ParamType是個引用類型。由於引用的特點,即形參代表實參本身所以實參expr的CV屬性會被T保留下來。(注意,如果傳進來的實參是個指針,則param會將實參(指針)的頂層和底層const的都保留下來)。

    ②當ParamType為T*指針,傳參時param指針是實參的副本,因此實參和形參是兩個不同的指針。T在推導中,會保留實參中的底層const,而舍棄頂層const。因為底層const修飾的是指針所向對象,表示該對象不可更改,所以const應保留,而頂層const表示指針本身,從實參到形參傳遞時復制的是指針,因此形參的指針是個副本,無須保留const屬性。(如,const char* const ptr中,頂層const指修飾ptr的const,即*號右側const,而底層const指*號左側的const)

(二)規則2:ParamType為萬能引用,即T&&

1、兩條規則:

    ①如果實參expr是個左值,T和ParamType會被推導為左值引用。(注意,這是模板類型推導中,T唯一被推導為引用(T&)的情形

    ②如果要實參expr是個右值,則應用“規則1”來推導。 此時的T被推導為T

2、注意事項:

    ①當實參為左值時T被推導為T&(不是T類型),表示形參是實參的引用,即代表實參本身。因此指針的頂層和底層const屬性均會被保留

    ②形如const T&&或vector<T>&&)均屬於右值引用,因為const的引用會剝奪引用成為萬能引用的資格,因為由其定義的變量再也不能成為非const類型,所以不是“萬能”的類型,而后者己經確定是個vector<T>類型,不可能成為“萬能”的類型,如不會再是int型,因此也不是萬能引用。(詳見《萬能引用》一節)

    ③在推導過程中會發生引用折疊(詳見《引用折疊》一節)。

(三)規則3:ParamType是個非指針也非引用類型(即按值傳遞

1、推導規則:

    ①若實參是個引用類型,則忽略其引用部分,同時cv屬性被忽略。(因為按值傳遞,采用復制手段,形參是個副本,與實參是兩個不同的參數)。

    ②如果實參是指針類型,從實參到形參傳遞時,傳用的是按比特位復制,因此形參也是個副本,只保留指針的底層const,而舍棄頂層const)

    ③其他情況也是按值傳遞,形參同樣也是一個副本

【編程實驗】函數模板的推導規則

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;
using boost::typeindex::type_id_with_cvr;

//輔助類模板,用於打印T和param的類型
template <typename T, typename Param>
void printType()  
{
    cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", ";
    cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl;
}

/***************************規則1:當ParamType引用或指針時*****************************/  
//(1)當ParamType為T&時
template <typename T>
void func_r(T& param)  //由於T是個引用,操作的是變量/對象本身,當如果實參帶有const或volatile屬性,會被T保留下來
{                      //。如果實參是個指針,param代表實參本身,會將頂層和底層const全部保存一下。
    printType<T, decltype(param)>();
}

//(2)當ParamType為const T&時
template <typename T>
void func_cr(const T& param)  //由於假定param是個const引用,為避免重復,T的推導結果就沒必要包含const了。
{
    printType<T, decltype(param)>();
}

//(3)當ParamType為右值引用時
template <typename T>
void func_rr(const T&& param)  //注意由於T&&被const修飾,為右值引用(而非萬能引用)
{
    printType<T, decltype(param)>();
}

//(4)當ParamType為T*指針時(注意const char* const ptr)
//頂層const:指ptr指針變量本身的const屬性(T推導時被舍棄)
//底層const:指ptr指針所指向對象/變量的const屬性(被T保留)
template <typename T>
void func_p(T* param)  //當param是個指針時,如果實參帶const,則表示所指對象具有const屬性,因此其底層const會被T保留
{
    printType<T,decltype(param)>();
}

//(3)當ParamType為const T*指針時
template <typename T>
void func_cp(const T* param)  //如果實參帶有const,為避免重復,T的推導就沒有必要包含const
{
    printType<T, decltype(param)>();
}

/***************************規則2:當ParamType為萬能引用時,即T&& *****************************/
template<typename T>
void func_ur(T&& param)
{
    printType<T, decltype(std::forward<T>(param))>();
}

template<typename T>
void func_cr2(const T&& param) //注意這是右值引用
{
    printType<T, decltype(param)>();
}

/***************************規則3:當ParamType為非指針和引用類型時 *****************************/
template<typename T>
void func_v(T param)
{
    printType<T, decltype(param)>();
}

int main()
{
    int x = 27;

    const int cx = x;
    const int& rx = x;
    const int* const ptr = &x;

    cout << "****************************規則1*****************************" << endl;
    //ParamType為引用: void func_r(T& param)
    func_r(x);   //T = int, param = int&
    func_r(cx);  //T = const int, param = const int&
    func_r(rx);  //T = const int, param = const int&
    func_r(ptr); //T = const int*, param = const int* const; //這里表示實參(ptr)的引用, 即代表實參本身,因此ptr的頂、底層const均保留

//Param為const T&: void func_cr(const T& param) func_cr(x); //T = int, param = const int& func_cr(cx); //T = int, param = const int& func_cr(rx); //T = int, param = const int& //Param為指針: void func_p(T* param) func_p(&x); //T = int, param = int* func_p(&cx); //T = const int, param = const int* func_p(ptr); //T = const int, param = const int* //只保留底層const。(注意形參param是實參的副本,即將ptr按比特位復制給param, //因為是副本,所以只保留底層const) //Param為const T* : void func_cp(const T* param) func_cp(&x); //T = int, param = const int* func_cp(&cx); //T = int, param = const int* func_cp(ptr); //T = int, param = const int* //只保留底層const //ParamType為右值引用: void func_rr(T&& param) func_rr(10); //T = int, param = int&& func_rr(std::move(cx)); //T = int, param = const int&& func_rr(std::move(rx)); //T = int, param = const int&& cout << "****************************規則2*****************************" << endl; //void func_ur(T&& param); 注意,當實參為左值時,T一定是一個引用類型(左值引用),這是T被推導為引用的唯一情形 func_ur(x); // T = int&, param = int& func_ur(cx); // T = const int&, param = const int& //T為引用,會保留cv屬性 func_ur(rx); // T = const int&, param = const int& func_ur(ptr); // T = const int* const&, param = const int* const& //頂層和底層const均保留 func_ur(10); // T = int, param = int&& 實參為右值,T被推導為int,注意不是int&& func_ur((int&&)10); // T = int, param = int&& 實參為右值,T被推導為int,注意不是int&& //func_cr2(x); // 編譯不通過,因為const T&&代表右值引用(而非萬能引用),必須傳入右值。 func_cr2(10); // T = int, param = const int&& 實參為右值,T被推導為int cout << "****************************規則3*****************************" << endl;
 //按值形參: void func_v(T param);  
func_v(ptr); //T = const int*, param = const int* (注意只保留底層const,即實參本身的const舍棄)

return 0;
}

三、數組和函數實參的推導規則

(一)推導規則:

(1)當函數模板為按值形參時(如T param):數組和函數類型均退化成指針類型。(如char*、void(*)(int, double)。

    //由於數組到指針的退化規則,以下兩個函數等價的,所以不能同時聲明這兩個同名函數。

    void myFunc(int param[]);

    void myFunc(int* param);

(2)當函數模板為引用類型形參時(如T&  param):則數組和函數分別被推導為數組引用和函數引用類型。特別值得注意,數組引用類型會包含數組元素類型及大小信息,如const char(&)[13]。而函數引用類型如void(&)(int, double)

(二)數組引用的妙用:用於推導數組元素個數

【編程實驗】數組實參與函數實參的推導

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;
using boost::typeindex::type_id_with_cvr;

//輔助類模板,用於打印T和param的類型
template <typename T, typename Param>
void printType()
{
    cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", ";
    cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl;
}

//數組和函數實參的推導
template<typename T>
void func_v(T param)  //按值形參
{
    printType<T, decltype(param)>();
}

template<typename T>
void func_r(T& param)  //按引用形參
{
    printType<T, decltype(param)>();
}

void test(int x, double y)
{
}

//數組引用的妙用:獲得數組元素的個數
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept //注意形參是個數組引用
{
    return N;
}

int main()
{
    const char name[] = "SantaClaus"; //name的類型為const char[11];(含\0)
    const char* pName = name;         //數組退化為指針

    /********************************數組和函數實參的推導****************************************/
    func_v(name);  //T = const char*, param = const  char*(數組退化為指針,因此T是指針類型)
    func_r(name);  //T = const char[11], param = const char(&)[11]

    func_v(test);  //T = void(*)(int, double), param = void(*)(int, double) (函數名退化為指針,因此T是指針類型)
    func_r(test);  //T = void(int, double), param = void(&)(int, double) (注意T的類型)

    /***********************************數組引用的妙用******************************************/
    int keyValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int mapVals[arraySize(keyValues)]; //arraySize返回值是編譯期常量,可用於定義數組大小

    cout << arraySize(keyValues) << endl; //9
    cout << arraySize(mapVals) << endl;   //9
    cout << arraySize(name) << endl;      //11
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM