模板型别推导
模板型别推导
下面代码表示:函数模板和函数调用(从 expr 来推导 T 和 ParamType 的型别)。
template<typename T> void func(ParamType param); // 函数模板的声明 func(expr); // expr:表达式,从 expr 来推导 T 和 ParamType 的型别
在编译期,编译器会通过 表达式 expr 推导两个型别(类型),一个是 T 的型别,一个是 param 的型别,而且这两个型别通常不一样。因为 ParamType 中常常会包含一些饰词,如 const 或引用符号等限定词,例如 ParamType 可以是 const T&。
本质上, T 的型别推导结果,不仅仅依赖于 expr 的型别,还依赖 ParamType 的形式,具体情况分为三种:
- ParamType 具有指针或引用型别,但不是万能引用;
- ParamType 是一个万能引用;
- ParamType 既非指针,也非引用;
情况1:ParamType 具有指针或引用型别,但不是万能引用;
在这种情况下,编译器会这样进行类型推导:
- 若 expr 具有引用型别,先将引用部分忽略。
- 然后,对 expr 的型别和 ParamType 的型别执行模式匹配,来决定的 T 型别.
举例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | template < typename T> void func(T& param) { T t = 0; cout << type_name< decltype (t)>() << endl; // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别 cout << type_name< decltype (param)>() << endl; } int main() { int x = 27; const int cx = x; const int & rx = x; func(x); // T 的型别是 int,param 的型别是 int& func(cx); // T 的型别是 const int,param 的型别是 const int& func(rx); // T 的型别是 const int,param 的型别是 const int&} |
当人们向引用型别的形参传入 const 对象时,他们期望对象保持其不可修改的属性,也就是说,期望该形参成为 const 的引用型别。即该对象的常量性(constness)会成为 T 的类型推导结果的组成部分。
在第三个调用中,即使 rx 具有引用型别,T 也并未被推导成一个引用。因为 rx 的引用性(reference-ness)会在型别推导过程中被忽略。
上述规则对于右值引用仍然成立!
如果将函数模板的形参类别从 T& 改为 const T&,结果会有一些变化,但仍然满足规则,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | template < typename T> void func( const T& param) { T t = 0; cout << type_name< decltype (t)>() << endl; // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别 cout << type_name< decltype (param)>() << endl; } int main() { int x = 27; const int cx = x; const int & rx = x; func(x); // T 的型别是 int,param 的型别是 const int& func(cx); // T 的型别是 int,param 的型别是 const int& func(rx); // T 的型别是 int,param 的型别是 const int& } |
一如前例,rx 的引用性在型别推导过程中会被忽略,并且函数模板的参数 param 本身具有 const 引用型别,T 的型别推导结果中包含 const 也就没有必要了。
情况2:ParamType 是万能引用;
当函数模板中的参数是万能引用(即在函数模板中持有型别参数 T 时,万能引用的声明有且只能为 T&&)时,当传入的实参为左值或右值时,表现有所不同。具体如下:
- 如果 expr 是个左值,T 和 ParamType 都会被推导为左值引用。有两点特别之处:1. 这是在模板型别推导中,被推导为引用型别的唯一情形;2. 函数模板形参是用右值引用语法声明,而它的型别推导结果却是左值引用。
- 如果 expr 是个右值,推导结果如情况1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | template < typename T> void func(T&& param) { int m = 0; T t = m; cout << type_name< decltype (t)>() << endl; // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别 cout << type_name< decltype (param)>() << endl; } int main() { int x = 27; const int cx = x; const int & rx = x; func(x); // T 的型别是 int&,param 的型别是 const int& func(cx); // T 的型别是 const int&,param 的型别是 const int& func(rx); // T 的型别是 const int&,param 的型别是 const int& func(27); // T 的型别是 int,param 的型别是 int&& } |
情况3:ParamType 既非指针也非引用;
当 ParamType 既非指针也非引用,此时就是值传递。无论传入的是什么, ParamType 都会是它的一个副本。由 expr 推导出 T 的型别,规则如下:
一如之前,若 expr 具有引用型别,则忽略;
忽略 expr 的引用型别后,若是个 const / volatile 对象,也忽略 const / volatile 属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | template < typename T> void func(T param) { T t = 0; cout << type_name< decltype (t)>() << endl; // 这里通过调用自定义的 type_name 函数来获取表达式(T 和 param)的型别 cout << type_name< decltype (param)>() << endl; } int main() { int x = 27; const int cx = x; const int & rx = x; func(x); // T 的型别是 int,param 的型别是 int func(cx); // T 的型别是 int,param 的型别是 int func(rx); // T 的型别是 int,param 的型别是 int } |
有上述代码可知:若函数模板参数的型别为 const T param,则 T 的型别仍是 int,param 的型别是 const int。
对于函数实参为既有顶层 const 也有底层 const的对象,那么按值传递会忽略对象的顶层 const 属性,而保留底层 const 属性。如下述代码所示:
1 2 3 4 5 6 | template < typename T> void func(T param); const char * const ptr = "czw study" ; func(ptr); // T 的型别是 const char*,param 的型别是 const char* |
数组实参
虽然数组有时会退化成首元素的指针,但当函数模板的参数是引用型别和非引用型别,有很大的区别。
- 当函数模板的参数不是引用型别时,数组会退化成指针;
- 当函数模板的参数是引用型别时,数组不会退化成指针;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | template < typename T> void func(T param); const char name[] = "czw study" ; func(name); // T 的型别是 const char*,param 的型别是 const char* template < typename T> void func(T& param); const char name[] = "czw study" ; // name 的型别是 const [10] const func(ptr); // T 的型别是 const char [10] const,param 的型别是 const char [10] const& |
因此,可以利用数组引用这一能力创造出一个模板,用来推导出数组含有元素的个数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 | // 该函数模板能在编译期获取数组的个数 template < typename T, std:: size_t N> constexpr std:: size_t arraySize(T (&)[N]) noexcept // 模板参数为数组引用: T (&)[N] { return N; } int main() { int keyVals[] = { 1, 3, 7, 9, 11 }; // 该数组的个数在初始化列表中计算得到 int mappedVals[arraySize(keyVals)]; // 编译成功,说明通过 arraySize 能够在编译期得到数组大小 } |
将 arraySize 声明为 noexcept,是为了帮助编译器生成更好的目标码。
函数实参
除了数组型别退化为指针外,函数型别也会退化为函数指针,并且相关规则与数组型别推导一致。
当函数模板参数按值传递,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void someFunc( int , double ) { cout << "123" ; } template < typename T> void func(T param) // param 按值传递 { cout << type_name< decltype (param)>() << endl; } int main() { func(someFunc); // param 被推导为函数指针,具体型别是: void (*)(int, double)<br>} |
当函数模板参数按引用传递,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | void someFunc( int , double ) { cout << "123" ; } template < typename T> void func(T& param) // param 按引用传递 { cout << type_name< decltype (param)>() << endl; } int main() { func(someFunc); // param 被推导为函数引用,具体型别是: void (&)(int, double) } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?