当前位置: 首页 > news >正文

C++模板梳理

目录

函数模板

类模板

变量模板

模板全特化

模板偏特化

模板显式实例化解决文件分离问题

折叠表达式

模板的二阶段编译

待决名(dependent name)

SFINAE

概念与约束


函数模板

函数模板不是函数,只有实例化的函数模板,编译器才能生成实际的函数定义,不过在很多时候,它看起来就像普通函数一样。

示例:

下面是一个函数模板,返回两个对象中较大的那个:

template<typename T>
T max(T a,T b){return a > b ? a : b;
}

这里其实对T有几个要求,1:有>运算符,比如内置的int,double; 2:返回了一个T,即要求T是可以移动或复制的。 如果函数模板实参不满足以上要求,则匹配不到此模板。

C++17 之前,类型 T 必须是可复制或移动才能传递参数。C++17 以后,即使复制构造函数和移动构造函数都无效,因为 C++17 强制的复制消除的存在,也可以传递临时纯右值。

使用模板

template<typename T>
T max(T a, T b) {return a > b ? a : b;
}int main(){int a{ 1 };int b{ 2 };max(a, b);          // 函数模板 max 被推导为 max<int>max<double>(a, b);  // 传递模板类型实参,函数模板 max 为 max<double>
}

模板函数可手动指定模板形参类型,也可以让编译器推导,即模板参数推导(template argument deduction),c++11支持函数模板参数推导,但是类模板参数推导要到c++17才支持。

对于编译无法推导的场景,可手动指定,如:

max<double>(1, 1.2);           
max<std::string>("luse"s, "乐");

但是 std::string 没有办法如此操作,编译器会报:

<source>:31:4: error: call of overloaded 'max(std::string, std::string)' is ambiguous

31 | max(string("luse"), string("乐"));

| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

<source>:23:3: note: candidate: 'T max(const T&, const T&) [with T = std::__cxx11::basic_string<char>]'

23 | T max(const T& a, const T& b) {

| ^~~

/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/stl_algobase.h:257:5: note: candidate: 'constexpr const _Tp& std::max(const _Tp&, const _Tp&) [with _Tp = __cxx11::basic_string<char>]'

257 | max(const _Tp& __a, const _Tp& __b)

即函数有二义性,这是因为自己所编写的max函数和标准库的max函数冲突了,但是int double实例化max函数并不会。原因是string在std命名空间,标准库的max函数也在std命名空间,虽然我们没有使用 std::,但是根据 C++ 的查找规则,(实参依赖查找)ADL,依然可以查找到。

那么我们如何解决呢?很简单,进行有限定名字查找,即使用 :: 或 std:: 说明,你到底要调用 “全局作用域”的 max,还是 std 命名空间中的 max。

::max(string("luse"), std::string("乐"));

有默认实参的模板类型形参

就如同函数形参可以有默认值一样,模板形参也可以有默认值。

template<typename T = int>
void f();f();            // 默认为 f<int>
f<double>();    // 显式指明为 f<double>using namespace std::string_literals;template<typename T1,typename T2,typename RT = decltype(true ? T1{} : T2{}) >RT max(const T1& a, const T2& b) { // RT 是 std::stringreturn a > b ? a : b;
}int main(){auto ret = ::max("1", "2"s);std::cout << ret << '\n';
}

让 max 函数模板接受两个参数的时候不需要再是相同类型,那么这自然而然就会引入另一个问题了,如何确定返回类型?

typename RT = decltype(true ? T1{} : T2{})

这是一个三目运算符表达式。然后外面使用了 decltype 获取这个表达式的类型,那么问题是,为什么是 true 呢?以及为什么需要 T1{},T2{} 这种形式?

1:我们为什么要设置为 true

其实无所谓,设置 false 也行,true 还是 false 不会影响三目表达式的类型。这涉及到了一些复杂的规则,简单的说就是三目表达式要求第二项和第三项之间能够隐式转换,然后整个表达式的类型会是 “公共”类型

比如第二项是 int 第三项是 double,三目表达式当然会是 double。

using T = decltype(true ? 1 : 1.2);
using T2 = decltype(false ? 1 : 1.2);

2:为什么需要 T1{}T2{} 这种形式?

没有办法,必须构造临时对象来写成这种形式,这里其实是不求值语境,我们只是为了写出这样一种形式,让 decltype 获取表达式的类型罢了。

模板的默认实参的和函数的默认实参大部分规则相同。

decltype(true ? T1{} : T2{}) 解决了。

事实上上面的写法都十分的丑陋与麻烦,我们可以使用 auto 简化这一切。

template<typename T,typename T2>
auto max(const T& a, const T2& b) -> decltype(true ? a : b){return a > b ? a : b;
}

这是 C++11 后置返回类型,它和我们之前用默认模板实参 RT 的区别只是稍微好看了一点吗?

不,它们的返回类型是不一样的,如果函数模板的形参是类型相同 true ? a : b 表达式的类型是 const T&;如果是 max(1, 2) 调用,那么也就是 const int&而前面的例子只是 T 即 int(前面都是用模板类型参数直接构造临时对象,而不是有实际对象,自然如此,比如 T{})。

使用 C++20 简写函数模板,我们可以直接再简化为:

decltype(auto) max(const auto& a, const auto& b)  {return a > b ? a : b;
}

效果和上面使用后置返回类型的写法完全一样;C++14 引入了两个特性:

  1. 返回类型推导(也就是函数可以直接写 auto 或 decltype(auto) 做返回类型,而不是像 C++11 那样,只是后置返回类型。

  2. decltype(auto) “如果返回类型没有使用 decltype(auto),那么推导遵循模板实参推导的规则进行”。我们上面的 max 示例如果不使用 decltype(auto),按照模板实参的推导规则,是不会有引用和 cv 限定的,就只能推导出返回 T 类型。即直接写auto会丢弃CV限定符,但decltype(auto)按decltype的规则推导类型,会保留CV限定符。

非类型模板形参

既然有”类型模板形参“,自然有非类型的,顾名思义,也就是模板不接受类型,而是接受值或对象。

template<std::size_t N>
void f() { std::cout << N << '\n'; }f<100>();

非类型模板形参有众多的规则和要求,目前,你简单认为需要参数是“常量”即可。

非类型模板形参当然也可以有默认值:

template<std::size_t N = 100>
void f() { std::cout << N << '\n'; }f();     // 默认      f<100>
f<66>(); // 显式指明  f<66>

重载函数模板​

函数模板与非模板函数可以重载。

这里会涉及到非常复杂的函数重载决议,即选择到底调用哪个函数。

我们用一个简单的示例展示一部分即可:

template<typename T>
void test(T) { std::puts("template"); }void test(int) { std::puts("int"); }test(1);        // 匹配到test(int)
test(1.2);      // 匹配到模板
test("1");      // 匹配到模板
  • 通常优先选择非模板的函数。

可变参数模板

和其他语言一样,C++ 也是支持可变参数的,我们必须使用模板才能做到。

老式 C 语言的变长实参有众多弊端,参见。

同样的,它的规则同样众多繁琐,我们不会说太多,以后会用到的,我们当前还是在入门阶段。

我们提一个简单的需求:

我需要一个函数 sum,支持 sum(1,2,3.5,x,n...) 即函数 sum 支持任意类型,任意个数的参数进行调用,你应该如何实现?

首先就要引入一个东西:形参包

本节以 C++14 标准进行讲述。

模板形参包是接受零个或更多个模板实参(非类型、类型或模板)的模板形参。函数形参包是接受零个或更多个函数实参的函数形参。

template<typename...Args>
void sum(Args...args){}

这样一个函数,就可以接受任意类型的任意个数的参数调用,我们先观察一下它的语法和普通函数有什么不同。

模板中需要 typename 后跟三个点 Args,函数形参中需要用模板类型形参包后跟着三个点 再 args。

args 是函数形参包,Args 是类型形参包,它们的名字我们可以自定义。

args 里,就存储了我们传入的全部的参数,Args 中存储了我们传入的全部参数的类型。

那么问题来了,存储很简单,我们要如何把这些东西取出来使用呢?这就涉及到另一个知识:形参包展开。

void f(const char*, int, double) { puts("值"); }
void f(const char**, int*, double*) { puts("&"); }template<typename...Args>
void sum(Args...args){  // const char * args0, int args1, double args2f(args...);   // 相当于 f(args0, args1, args2)f(&args...);  // 相当于 f(&args0, &args1, &args2)
}int main() {sum("luse", 1, 1.2);
}

类模板

变量模板

模板全特化

模板偏特化

模板显式实例化解决文件分离问题

折叠表达式

模板的二阶段编译

待决名(dependent name)

SFINAE

概念与约束

http://www.xdnf.cn/news/382483.html

相关文章:

  • JAVA练习题(3) 开发验证码
  • 如何避免软件腐朽
  • jflash下载时出现 Could not read unit serial number! 的解决方法
  • 数据结构—(概述)
  • 【typenum】 1 说明文件(README.md)
  • 【AI论文】迈向多模态通才之路:通用层级与通用基准
  • 一文讲透MCP的原理及实践
  • Kubernetes生产实战(十二):无工具容器网络连接数暴增指南
  • 【Day 24】HarmonyOS端云一体化开发:云函数
  • C PRIMER PLUS——第8节:字符串和字符串函数
  • 初等数论--欧拉定理及证明
  • 计算最短路径的数量模板(最短路)
  • 【智能指针】
  • 前端项目中单元测试与集成测试的管理实践
  • 基于51单片机的模拟洗衣机控制面板proteus仿真
  • JavaScript篇:async/await 错误处理指南:优雅捕获异常,告别失控的 Promise!
  • Java并发编程,从线程安全到死锁避免的实战解析
  • Java代码日志嵌入打包时间
  • 【排错】dify1.3.1插件市场安装报错问题
  • 《从零开始:构建你的第一个区块链应用》
  • 什么是文件描述符(File Descriptor,FD)
  • 45.中医知识问答管理员端对话信息查看功能bug修复(1)
  • 在 Vue 3 中实现刮刮乐抽奖
  • 进阶 DFS 学习笔记
  • 地学领域中常见的数据类型总结
  • 游戏服务器出现卡顿该怎么处理?
  • 学习黑客5 分钟深入浅出理解Linux Logs [特殊字符]
  • 【C++】string类
  • leetcode0829. 连续整数求和-hard
  • CountDownLatch 并发编程中的同步利器