模板参数推导
模板参数推导(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.0 1.1 Template argument deduction (C++ only), in IBM XL C/C++ Reference for Linux
- ^ 模板的模板参数,例如:Stack<int,std::vector<int> > vStack; // integer stack that uses a vector as internal container
- ^ § 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.