Effective Modern C++ 系列之 條款1: 理解模板型別推導

Effective Modern C++ 系列之 條款1: 理解模板型別推導

1. CPP模板類型推導

函數模板一般形式如下:

<code>template<typename> 
void func(ParamType parm)/<typename>/<code>

而調用形式如下:

<code>func(expr);/<code>
<code>在編譯期,編譯器會通過expr推導二個型別: 一個是T的型別,另一個是ParamType的型別./<code>

T 和 ParamType,這二個往往不一樣。 因為ParmType一般會包含一些修辭符.

T的型別不僅依賴expr的型別,還依賴ParamType的形式. 具體分為三種情況:

  • 情況一: ParamType具有指針或者引用型別,但不是個萬能.
  • 情況二: ParamType是個萬能引用.
  • 情況三: ParamType既非指針也非引用.

1.1 ParamType具有指針或者引用型別,但不是個萬能

此種情況下推導規則:

  • 若expr是引用類型,則先將引用部分忽略.
  • 然後,對expr的型別和ParamType的型別執行模式匹配,來決定T的類型.

1.1.1 param是個引用

<code>template<typename>
void f(T& param);/<typename>/<code>
<code>變量聲明:/<code>
<code>int x = 27;
const int cx = x;
const int& rx = x;/<code>

下面的推導的結果如下

  • f(x) : T-> int, param -> int&
  • f(cx) : T-> const int, param -> const int&
  • f(rx) : T-> const int, param -> const int&

注意:rx的引用會在推導過程中被忽略.

1.1.2 param是個指針(或指向const對象的指針)

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

int x = 27;
const int* px = &x; /<typename>/<code>
  • f(&x) : T -> int, param -> int*
  • f(px) : T -> const int, param -> const int*

C++的型別推導規則對於引用和指針形參都適用

1.2 ParamType是個萬能引用

萬能引用形參模板推導規則:

  • 如果expr是個左值,T 和 paramType都會被推導微左值引用.
  • 如果expr是個右值, 則應用情況1的規則.

例如:

<code>template<typename>
void f(T&& param); //param現在是萬能引用了

int x = 27;
const int cx = x;
const int& rx = x;/<typename>/<code>
  • f(x) : x是左值, T -> int& , param -> int&
  • f(cx): cx是左值, T -> const int&, param -> const int&
  • f(rx): rx是左值, T->const int& , param-> const int&
  • f(27): 27是右值, T -> int, param-> int&&.

當遇到萬能引用時,型別推導規則會區分實參時左值還是右值.

1.3 ParamType既非指針也非引用

當ParamType既非指針也非引用時,我們面對的就是所謂按值傳遞了

<code>template<typename>
void f(T param); //param 是按值傳遞.

f(expr); //調用f函數./<typename>/<code>

在c++中按值傳遞param會是一個副本,推導規則如下:

  • 若 expr具有引用型別,則忽略其引用的部分.
  • 忽略expr的引用性之後,若expr是個const或volatile對象,也忽略之.

例子:

<code>int x = 27;
const int cx = x;
const int& rx = x;/<code>
  • f(x) : T -> int, param -> int.
  • f(cx) : T -> int, param -> int.
  • f(rx) : T -> int, param -> int.

注意:按值傳遞 param是個完全獨立與cx和rx存在的對象,cx和rx的一個副本. expr的不可修改,並不能斷定其副本也不可以修改.

const和volatile僅會在按值形參處忽略. 若形參時const的引用或者指針,expr的常量性會在推導過程中加以保留

特殊案例:expr是個指向到const對象的const指針,且expr按值傳遞給param.

<code>template<typename>
void f(T param);
const char* const ptr = "fun with pointers"; /<typename>/<code>
  • f(ptr) - T -> const char, param -> const char型別.

1.4 數組實參

數組型別有別於指針類型,儘管有時看起來它們可以互換. 形成這種假象的主要原因,在很多情況下,數組會退化成指向其首元素的指針.

<code>const char name[] = "J. P Briggs";   //name的型別時const char[13].
const char* ptrToName = name; //ptrToName的型別是const char* 。/<code>

上述代碼在編譯器上是能通過編譯的.

1.4.1 數組傳遞給持有按值形參的模板

<code>template<typename>
void f(T param); //持有按值形參的模板./<typename>/<code>
  • f(name): T -> const char, param -> const char型別.

1.4.2 數組傳遞給按引用傳遞形參的模板

<code>template<typename> 
void f(T& name); //按引用方式傳遞形參的模板./<typename>/<code>
  • f(name): T -> const char[13], param -> const char(&)[13].

可以利用聲明數組引用這一能力創造出一個模板,用來推導處數組含有的元素個數.

<code>template<typename> 
constexpr std::size_t array_size(T (&)[N]) noexcept {
return N ;
}/<typename>/<code>

例如:

<code>int keyVals[] = {1,2,3,4,5,5,6};
int mappedVals[array_size(keyVals)];/<code>

在現代C++中,優先選用std::array:

<code>std::array mappedVals;/<code>

1.5 函數實參

在C++中, 函數型別也同樣會退化為函數指針. 並且數組型別推導規則適用函數及函數指針的退化.

<code>void someFunc(int, double) // someFunc 是個函數, 其型別為void(int,double);/<code>

1.5.1 持有按值形參傳遞的模板

<code>template<typename> 
void f1(T param);/<typename>/<code>
  • f1(somFunc): T-> void()(int ,double) , param ->void()(int ,double).

1.5.2 持有按引用形參傳遞的模板

<code>template<typename> 
void f1(T& param);/<typename>/<code>
  • f1(somFunc): T-> void(int ,double) , param ->void(&)(int ,double).

2. 總結

  • 在模板型別推導過程中, 具有引用型別的實參會被當成非引用型別來處理,換言之,其引用性會被忽略.
  • 對萬能引用形參進行推導時,左值實參會進行特殊處理.
  • 對按值傳遞的形參進行推導時,若實參型別中帶有const或volatile修辭詞,則它們還是會被當作不帶const或者volatile的型別來處理.
  • 在模板型別推導過程中,數組或者函數型別的實參會退化成對應的指針,除非它們被用來初始化引用.

3. 代碼使用boost庫中type_index

部分代碼如下:

<code>#include <boost>
#include <iostream>
#include <array>

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

template<typename>
void f(T& param) {
std::cout << "T =" << type_id_with_cvr().pretty_name() << std::endl;
std::cout << "param =" << type_id_with_cvr<decltype>().pretty_name() << std::endl;
}

void someFunc(int, double) {
}

template<typename>
constexpr std::size_t array_size(T(&)[N]) noexcept {
return N;
}

int main(int argc,char** argv) {
#if 0
int x = 27;
const int cx = x;
const int& rx = x;
f(x);
f(27);
#endif
//char const * const ptr = "fun with pointers";
//f(ptr);
//const char name[] = "J. P. Briggs";
//f(name);
//int keyVals[] = { 1,2,3,4,5,5,6 };
//std::array mappedVals = { 1,2,3,4,5,5,6 };
//for(auto e: mappedVals)
//{
// std::cout << e << std::endl;
//}
f(someFunc);
return 0;
}
/<typename>/<decltype>
/<typename>/<array>/<iostream>/<boost>/<code>

本文學習《 Effective Modern C++ 》一書整理的學習筆記


分享到:


相關文章: