模板參數推導

模板參數推導(template argument deduction),是在調用C++模板函數時,由編譯器根據使用上下文來推斷所調用的模板函數的模板參數。[參1]這一概念也適用於類的模板成員函數。

類模板也存在模板參數推導的情形。例如:

template <class T> struct eval;
template <template <class, class...> class TT, class T1, class... Rest>
     struct eval<TT<T1, Rest...>> { };
eval<A<int>> eA; // OK: matches partial specialization of eval

概念

編輯

模板函數在定義時,用template<>聲明模板參數(或稱模板形參)。調用模板函數時,可以在函數名字後用< >顯式指出模板參數(這時稱作模板實參)。例如:

 template<class T> void foo(T v1){};
 foo<double>(3);

但是,調用模板函數時,如果不顯式指明模板參數,而是根據函數的調用實參去推斷模板實參,這就是模板參數推導。例如上例可進一步考慮情形:

 foo(3.14);

編譯器會推導出這種函數調用的模板實參為T->double.

推導類型

編輯

編譯器比較函數模板的形參(template parameter)與對應的調用實參(argument used in the function call)的類型,以確定模板參數的類型。形參的類型必須是下述特定情形之一:[參1]

T
const T
volatile T
T&
T*
T[10]
A<T>
C(*)(T)
T(*)()
T(*)(U)
T C::*
C T::*
T U::*
T (C::*)()
C (T::*)()
D (C::*)(T)
C (T::*)(U)
T (C::*)(U)
T (U::*)()
T (U::*)(V)
E[10][i]
B<i>
TT<T>
TT<i>
TT<C>

說明:

  • T, U, V表示模板類型參數
  • 10表示任意整數常量
  • i表示模板非類型參數
  • [i]表示數組界( represents an array bound of a reference or pointer type, or a non-major array bound of a normal array)
  • TT表示模板的模板參數(template template argument)[參2]
  • (T), (U), (V)表示參數列表包含至少一個模板類型參數
  • ()表示參數列表不包含模板參數
  • <T>表示模板參數列表包含至少一個模板類型參數
  • <i>表示模板參數列表包含至少一個模板非類型參數
  • <C>表示模板參數列表其模板參數不依賴於模板實參

編譯器可以從上述幾個類型結構的複合類型推導模板參數。

推導規則

編輯

對於形如:

template<typename T> int foo(ParamType param);

其模板參數T的類型需要從模板函數的實參與形參依照如下規則推導:

  • 首先,如果模板函數的實參表達式是引用,首先去除引用;
  • 上一步後,如果剩下的實參表達式有頂層的const且/或volatile限定符,去除掉。

因而,從模板函數的實參表達式,不能自動推導出頂層的CV-qualifiers,也不能自動推導出引用類型,需要顯式指定。

例如:

   template<class T> void foo(T arg){ arg=101;} // 函数模板     
   const int i=102;
   foo(i); //函数模板实例化为 void foo<int>(int),实参的const限定已被脱去
   foo<const int&>(v1);//直接显示指明模板参数类型
   template<class T> void foo(const T& arg);//或者偏特化模板函数

如果形參還帶上&號,聲明為引用類型,則不執行const剝除(const-stripping),例如:

   const int i=102;
   const int &j=i;
   template<class T> void foo(T& arg) { arg=101;}// 函数模板 
   foo(j); // 编译错误: 'arg': you cannot assign to a variable that is const	 

這是因為如果不抑制const剝除,則得到了一個非常量引用型變量,綁定到const變量,這顯然是不可接受的。

實參表達式為數組,模板參數推導的類型為指針。這是因為數組名在實參表達式中自動隱式轉換為首元素地址的右值。例如:

   int a[9];
   template<class T> void foo(T arg){}; // 函数模板 
   foo(a);// 函数模板实例化为 void foo<int*>(int*)

另外,C++11標準明確規定不能由模板參數推導出對應實參為std::initializer_list的類型。[參3]例如:

template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T

類型完美轉發

編輯

C++11增加了右值引用這一新的數據類型。如:template<class T> void foo(T&& arg);「T&&」並不意味着形參arg的數據類型一定是右值引用。其數據類型既可能是左值引用,也可能是右值引用。依據「引用塌縮規則」,有:

  • 如果實參表達式是類型A的左值,則模板參數T的類型為左值引用A&,形參arg的類型為左值引用A&;
  • 如果實參表達式是類型A的右值(包括純右值臨終值),則模板參數T的類型為右值引用A&&,形參arg的類型為y右值引用A&&。

如此,就把實參的值分類情形完美地傳遞到模板函數內部,據此可再完美轉發給拷貝語義或移動語義的實現函數。

參考文獻

編輯

    參:

  1. ^ 1.0 1.1 Template argument deduction (C++ only), in IBM XL C/C++ Reference for Linux
  2. ^ 模板的模板參數,例如:Stack<int,std::vector<int> > vStack; // integer stack that uses a vector as internal container
  3. ^ § 14.8.2.5/5 of the C++11 standard explicitly states that this is a non-deduced context for a template argument: A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type.