Linux
Linux software install
Google repo的使用
Vector AP StartApplication编译脚本解析
Yocto的cmake版本升级
不能自动安装的解决方式
S32G-BSP35.0-SDK使用方法
S32G从SDK生成文件系统的制作过程
Linux Samba设置
Linux添加双网卡
S32G USB-Redirector安装指南
VS code自动生成Doxygen格式注释
Linux下使用多线程下载
使用pandoc 生成带中文的pdf
minicom无法输入的问题解决办法
使用 systemd unit 添加一条路由规则
CMake 教程
步骤 1:基本起点
步骤 2:添加lib库
步骤 3:为库添加使用要求
步骤 4:添加生成器表达式
步骤 5:安装和测试
步骤 6:添加支持测试仪表板
步骤 7: 添加系统内省
步骤 8:自定义命令和生成的文
步骤 9:打包安装程序
步骤 10:选择静态库或共享库
步骤 11:添加导出配置
步骤 12:打包 Debug 和 Release
添加虚拟网卡
Vector AP 去掉防篡改校验
Vector AP startapplication编译与使用
Vector AP问题汇总
Vector AP大型项目开发流程
Vector AP EM
Vector AP 最简单的开发示例
Linux kernel 版本降级
Vector AP StartApplicaiton
startappplication-machine-design
startapplicaiton-machine-integration
amsr-vector-fs-em-executionmanager-design
amsr-vector-fs-em-executionmanager
Vector AP 复杂模型的开发
第一章 Machine和MachineDesign
第二章 Execute Manager
第三章 Log
第四章 State Manager
第五章 State Manager 源码的理解
第六章 Someip daemon
第七章 IPC Service Discovery Daemon
crond的使用方法
解决蓝牙鼠标在 Ubuntu 中单位时间内断开的问题
VPS服务器自建教程
v2rayA的客户端使用配置
GDB调试指南入门篇:揭开程序运行世界的神秘面纱
GDB调试指南高级篇:成为调试专家,掌控程序的命运
Linux安装PyQt5并配置Qt Designer
ADB 命令大全
GoogleTest(一)
GoogleTest(二)简单的TEST宏
GoogleTest(三)简单类函数测试
C++ Template
1. 函数模板
2. 类模板
3. 非类型模板参数
软件版本号规范
EPOLL
C++手札
C++ 使用{}初始化有哪些好处?
现代 C++ decltype 深入解析
函数对象(functor)
Linux性能剖析:CPU、内存、网络与I/O压力测试
AP StateManager
C++ Lambda表达式
C++ 中的Lambda表达式
Lambda 表达式语法
Lambda 表达式的示例
手动发送UDP数据包
pyqt5生成的UI界面不能输入中文
自己搭建repo镜像
摄影
Sony仿富士PP值设置
诗词歌赋
本文档使用 MrDoc 发布
-
+
首页
1. 函数模板
## 前言 本来不想做笔记的,书上画画线就当学过了。但是看完第一章发现,细枝末节的东西太多了。还是记录一下,以便于以后查阅。 本篇只是简介,高深的用法后续讨论 ## 定义 `template<由逗号分隔的参数列表>` eg: ```cpp template <typename T> //template<由逗号分隔的参数列表> T Max(T a, T b){ return (b < a ? a : b); } ``` `typename T`:参数列表 - `typename`:关键字typename是类型参数的引导字 - `T`:类型参数,可以是任意标识符,类似变量名 ## 引用 ```cpp #include <iostream> #include <string> int main(){ int i = 42; std::cout << "max(7,i): " << ::Max(7,i) << std::endl; double f1 = 3.4; double f2 = -6.7; std::cout << "max(f1,f2): " << ::Max(f1,f2) << std::endl; std::string s1 = "mathematics"; std::string s2 = "math"; std::cout << "max(s1,s2): " << ::Max(s1,s2) << std::endl; } ``` 模板不会编译成可以处理任何类型的单一实体,而是对于使用模板的每种类型,都会生成不同的实体。 即Max() 实际被编译成了`int Max(int a, int b)`、`double Max(double a, double b)`和`string Max(string a, string b)`三个。如果还有`Max('x', 'y')`的调用的话,将会再增加`char Max(char a, char b)`的第四个函数实体出现。 **想一想,如果是`void`参数,应该怎么引用** ```cpp template<typename T> T foo(T*) { } void* vp = nullptr; foo(vp); //正确:推导为void foo(void *)函数实体 ``` 这里切记不可使用foo(),编译器推导不出来参数类型 ## 两阶段编译 试图使用类型来实例化模板,而该类型不支持模板内使用的所有操作,就会导致编译错误。例如: ```cpp std::complex<float> c1, c2; //复数c1,c2。没有提供‘<’运算符 ... ::Max(c1, c2); //编译错误 ``` `Max`函数中需要用到`<`运算符,但是`std::complex`中并没有提供`<`运算符。所以编译会报错。 因此模板是分两阶段‘编译’ 1. 定义期间不会实例化,可以忽略模板参数,只检查模板代码自身的语法正确性。包含以下几个方面 - 发现语法错误,比如少了分号 - 发现使用独立于模板参数的未知名称(类型名,函数名等) - 检查独立于模板的静态断言 我将它理解为静态代码检查。 2. 实例化期间会再次检查模板代码,以确保所有代码的有效性。 我将它理解为链接状态检查。 ## 模板实参推导 ```cpp template <typename T> T Max(T a, T b){ return (b < a ? a : b); } ``` 如果传递两个int类型给参数类型T,编译器能得出T必定是int类型的结论。 但是T可能只是类型的‘部分’体现,例如我们使用常亮引用的Max() ```cpp template <typename T> T Max(T const &a, T const& b){ return (b < a ? a : b); } ``` 然后传递int,由于参数与int const & 匹配,T仍然会被推导为int。(其实就像个宏定义,用int去替换掉T) ### 类型推导中的类型转换 ***自动类型转换是受限的*** - 当声明调用函数是按引用传递时,及时在细微的转化也不适用于类型推导,使用相同模板参数T声明的两个参数必须完全匹配。 - 当声明调用参数是按值传递时,仅支持退化的简单转换。使用相同模板参数T声明的两个参数,退化后必须完全匹配。 - 忽略const和volatile限定符 - 引用转换为引用类型 - 原始数组或函数转换为相应的指针类型 eg: ```cpp template<typename T> T Max (T a, T b); ... int i = 3; int const c = 43; Max(i, c); //正确:T推导为int Max(c, c); //正确:T推导为int int & ir = i; Max(i, ir); //正确:T推导为int int arr[4]; foo(&i, arr); //正确:T推导为int * //以下是错误的 Max(4, 7.2); //错误,T可以推导为int或double std::string s; foo("hello", s); //错误,T可以推导为char[6]或std::string ``` 如何处理以上的错误: 1. 强制转换实参,使两者都匹配 ```cpp Max(static_cast<double>()4, 7.2); ``` 2. 显示指定(或限定)T的类型,以阻止编译器尝试进行类型推导 ```cpp Max<double>(4, 7.2); ``` 3. 指定参数可以有不同的类型,后续讨论 ### 默认实参类型推导 类型推导对默认调用实参不起作用。eg: ```cpp template<typename T> void f(T = ""); ... f(1); //正确:T推导为int, 因此调用f<int>(1) f(); //错误:无法推导T的类型 ``` 为了应对这种情况,还必须为模板参数声明一个默认实参(后续会讨论) ```cpp template<typename T = std::string> void f(T = ""); ... f(); //正确 ``` ## 多模板参数 目前我们已经看到函数模板有两组不同的参数 1. 模板参数,在函数模板名称前面的尖括号中声明 ```cpp template<typename T> //T是模板参数 ``` 2. 调用参数,在函数模板名称后面的圆括号中声明 ```cpp T Max(T a, T b) //a,b是调用参数 ``` 我们可以有任意多个模板参数。eg: ```cpp template<typename T1, typename T2> //T是模板参数 T1 Max(T1 a, T2 b) { return b < a ? a : b; } ... auto m = :: Max(4, 7.2); //正确,但第一个实参类型定义了返回类型,m 就是个int类型 ``` 这时会有一个问题,Max(66.66, 42)返回的是66.66,double类型。而 Max(42, 66.66)返回的是66,int类型。 C++提供了多种方法来解决这个问题: - 引入第三个模板参数作为返回类型 - 让编译器找出返回类型 - 将发挥类型声明为这两个参数类型的“公共类型” ### 返回类型参数模板 ```cpp template<typename T1, typename T2, typename RT> RT Max(T1 a, T2 b); ``` 由于RT没有用作调用参数,因此无法推导出返回类型,这时必须显示指定模板实参列表 ```cpp template<typename T1, typename T2, typename RT> RT Max(T1 a, T2 b); ... ::Max<int, double, double>(4, 7.2); //正确 ,但是太繁琐 ``` 还有一种方法是,显式指定第一个模板实参。需要把`typename RT`,换个位置,放到第一位,这样就可以只显式指定第一个模板实参类型 ```cpp template<typename RT, typename T1, typename T2> RT Max(T1 a, T2 b); ... ::Max<double>(4, 7.2); //正确: 返回类型是double,T1和T2 从实参推导出类型 ``` ### 推导返回类型 如果返回类型依赖于模板参数,那么推导返回类型最简单也是最好的方法就是让编译器来查找。从C++14开始,可以通过声明auto返回类型来实现 ```cpp template<typename T1, typename T2> auto Max(T1 a, T2 b) { return b < a ? a : b; } ``` C++11以后也可以使用尾置返回类型来实现 ```cpp template<typename T1, typename T2> auto Max(T1 a, T2 b) -> decltype(b < a ? a : b) { return b < a ? a : b; } ``` **说白了,需要记住一点,template是编译的时候就决定了参数类型的。因此对于`b < a ? a : b`这样的表达式,完全不用考虑比较条件的真假。只需要考虑bool类型,a的类型,b的类型哪个需要的内存空间大,就用哪个类。也许这才是最好记忆的一种方式** ### 返回类型为公共类型 从C++11开始,标准库提供了std::common_type<>::type的公共类型。eg: ```cpp #include <type_traits> template<typename T1, typename T2> std::common_type<T1, T2> max(T1 a, T2 b) { return b < a ? a : b; } ``` ## 默认模板参数 ```cpp template<typename RT = long, typename T1, typename T2> RT max(T1 a, T2 b) { return b < a ? a : b; } int i; long l; ... max(i, l); //返回long类型,typename RT = long,默认为long max<int>(4,3); //返回int类型,RT指定了int类型,T1,T2使用自动推导 ``` ## 重载函数模板 ```cpp #include <iostream> #include <string> int max(int a, int b){ std::cout << "max function no template" << std::endl; return b < a ? a : b; } template<typename T> T max(T a, T b){ std::cout << "max function with template" << std::endl; return b < a ? a : b; } int main(){ ::max(7, 42); //两个值都是int, 完美匹配非模板函数 ::max(7.0, 42.0); //调用max<double>()(通过实参推导) ::max('a', 'b'); //调用max<char>()(通过实参推导) ::max<>(7, 42); //调用max<int>()(通过实参推导) ::max<double>(7, 42); //调用max<double>() (无实参传导) ::max('a', 42.7); //调用两个int类型的非模板函数 } ``` 一个非模板函数可以和一个与其同名且可以用相同类型实例化的函数模板共存。在所有其他因素相同的情况下,重载解析过程优先选择非模板函数。而不是从模板函数实例化出来的函数。 再看一个都是模板函数的重载。 ```cpp #include <iostream> template<typename T1, typename T2> auto max(T1 a, T2 b){ std::cout << "2 template param function" << std::endl; return b < a ? a : b; } template<typename RT, typename T1, typename T2> RT max(T1 a, T2 b){ std::cout << "3 template param function" << std::endl; return b < a ? a : b; } int main (){ auto a = ::max(4, 7.2); //使用第一个模板 auto b = ::max<long, double>(7.2, 4); //使用第二个模板 //auto c = ::max<int>(4, 7.2); //错误:两个函数模板都匹配 } ``` 这个代码示例展示了模板函数的重载及其在类型推导和模板参数传递中的行为。我们来详细分析一下代码。 1. **第一个模板函数**: ```cpp template<typename T1, typename T2> auto max(T1 a, T2 b) { std::cout << "2 template param function" << std::endl; return b < a ? a : b; } ``` - 该函数接受两个模板参数 `T1` 和 `T2`,并自动推导返回类型。 - 当调用 `max(4, 7.2)` 时,`4` 会被推导为 `int` 类型,`7.2` 会被推导为 `double` 类型。返回类型 `auto` 会被推导为 `double`,因为 `int` 被提升为 `double`。 2. **第二个模板函数**: ```cpp template<typename RT, typename T1, typename T2> RT max(T1 a, T2 b) { std::cout << "3 template param function" << std::endl; return b < a ? a : b; } ``` - 该函数接受三个模板参数:返回类型 `RT` 和两个参数类型 `T1` 和 `T2`。 - 当调用 `max<long, double>(7.2, 4)` 时,`7.2` 被推导为 `double`,`4` 被推导为 `int`,但显式指定的返回类型是 `long`。所以即使 `7.2` 是 `double` 类型,返回类型仍然是 `long`。 3. **第三种调用(被注释掉的部分)**: ```cpp //auto c = ::max<int>(4, 7.2); //错误:两个函数模板都匹配 ``` - 在这个调用中,只有一个模板参数 `int` 被显式指定。这会导致编译器尝试匹配两个模板函数: - 第一个模板函数无法使用 `int` 作为返回类型(因为返回类型是 `auto` 推导的,无法显式指定)。 - 第二个模板函数可以接受 `int` 作为 `RT`,但缺少 `T1` 和 `T2` 的模板参数。 - 由于编译器找不到一个明确的匹配,所以会报错。 **总结:** - 调用 `max(4, 7.2)` 使用的是第一个模板函数,因为编译器可以自动推导出模板参数类型。 - 调用 `max<long, double>(7.2, 4)` 使用的是第二个模板函数,因为返回类型 `long` 被显式指定。 - 注释掉的调用 `max<int>(4, 7.2)` 产生编译错误,因为两个模板函数都部分匹配,但编译器无法决定到底使用哪一个。 你可以通过在调用模板函数时显式指定所有参数类型来避免这种模糊匹配。
admin
2024年8月9日 13:46
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码