C++ 的 CTAD 与推断指示(Deduction Guides)
1 类模板参数推导(CTAD)
1.1 曲线救国
CTAD 的全称是类模板参数推导(Class Template Argument Deduction),它允许在实例化类模板时,根据构造函数的参数类型自动推导模板参数,从而避免显式指定模板参数。CTAD 是在 C++ 17 引入的,在这之前,只有模板函数支持根据函数参数自动推导模板参数,类模板不支持这样的动作。代码中实例化类模板必须显式指定模板参数,十分不便,以致怨声载道。
C++ 11 引入了 auto,用作占位符衍生出了一种“工厂函数”惯用法,就是利用函数模板的推导规则,根据函数参数推导出模板参数,然后用推导出的模板参数实例化类对象。比如这个例子:
template<typename T, typename U>
class Foo {
public:Foo(T begin, U end) : m_begin(std::move(begin)), m_end(std::move(end)) {}
private:T m_begin;U m_end;
};template<typename T, typename U>
auto MakeFoo(T begin, U end) {return Foo<T, U>{begin, end};
}auto f2 = MakeFoo(42, 5.24);
1.2 隐式规则
C++ 17 的 CTAD 默认通过类模板的构造函数定义模板参数的推导规则,和函数模板一样,由构造函数的实参类型决定模板的参数类型。比如上一节的 Foo 类,不需要工厂函数,可以直接这样用:
Foo f1{42, 5.24};
但是编译器对类模板参数的推导是有条件的,那就是构造函数的形式参数列表必须能覆盖全部模板参数,并且这些形参都必须参与推导,不能有在非推导语境中的模板参数。简单来说,以下两个类模板就不支持 CTAD 隐式推导:
template<typename T, typename U>
struct Bar {Bar(const T& t) {}
};template<typename T, typename U>
struct Widget {Widget(const T& t, typename std::type_identity_t<U> u) {}
};Bar b1(42); //错误
Widget w(1, 2.3); //错误,不能实例化 Widget<int, double> 类型
Bar 的构造函数形参列表只覆盖了一个模板参数,另一个未知,不能通过构造函数同时推导出T 和 U 的类型。Widget 同样不支持 CTAD,它的构造函数形参覆盖了两个模板参数,但是 U 出现在典型的非推导语境中,它不参与推导,编译器不会根据实参 2.3 去推导 U 为 double,所以不能同时确定 T 和 U 的类型,也就无法实例化 Widget<int, double> 类型。
1.3 演化
CTAD 在 C++ 20 改善了一下对聚合类型的支持。对于聚合类型,可以在不提供显式构造函数的情况下,按照聚合类型的初始化顺序实现类型推导。我们假设下面例子中的 Foo 是个聚合类型,为什么假设呢?因为是不是聚合类型还要看它那三个成员的类型,我们这里给出的例子能确保 Foo 实例化后是个聚合类型。
template<typename T, typename U, typename V>
struct Foo {T t;U u;V v;
};Foo f{ 1, 2.3, "Hello" };
大括号中的参数,按照按照聚合类型的初始化顺序,以及传值类型模板形参的推导规则,依次与 t、u 和 v 匹配,推导出 T、U 和 V 的类型为 int、double 和 const char* ,并用 Foo<int, double, const char*> 类型初始化 f。
2 推断指示(Deduction Guides)
2.1 什么是推断指示
尽管 CTAD 可以根据构造函数参数自动推导模板参数,但有些复杂情况下,隐式的规则可能无法满足需求。此时我们可以利用 C++ 17 的显示推断指示(推断指引),通过提供自定义的模板参数推导规则,让编译器知道如何确定类模板的模板参数,从而实现复杂类模板参数的自动推导。
推断指示的语法大概是这个样子的:
//deduction-guide:
explicit(opt) template-name (parameter-declaration-clause) -> simple-template-id ;
explicit 关键字是可选的,用于说明是否是显式推断指示。这个语法的重点是 减号和大于号组成的箭头符号(->),箭头符号左边的 template-name 必须与箭头符号右边的 simple-template-id 具有相同的标识符。此外,如果一个 template-name 有多个推断指引,那么它们的 parameter-declaration-clause 不能相同。以 std::tuple 为例,看看它的推断指引的语法:
template<class... UTypes>
tuple(UTypes...) -> tuple<UTypes...>;
箭头符号的左边是 std::tuple 的构造函数(之一),其中 UTypes… 就是传递给构造函数的参数包(就是 parameter-declaration-clause)。箭头符号的右边是 std::tuple 的模板参数(simple-template-id),这个语法告诉编译器,可以根据构造函数的参数推断对应的类模板实例化使用的模板参数。
2.2 推断指示的典型用法
ContainerT 类有一个符合 CTAD 的构造函数,c1 就是通过这个构造函数提供的隐式规则,推导出 c1 的类型是 ContainerT。但是当我们希望传递一个大括号列表的时候,我们希望 T 是一个 vector 容器类型,此时构造函数提供的默认规则就无能为力了。c2 的定义会导致编译错误,因为模板形参推导不支持大括号列表(auto 的推导支持将大括号列表推导为具体的 std::initializer_list 类型,但这是个写死的规则,算不上推导)。
template <typename T>
class ContainerT {
public:ContainerT(T value) : val(value) {}T val;
};ContainerT c1(5); //ContainerT<int>
ContainerT c2({ 1, 5, 8 }); //错误
为了达成目标,我们需要为 ContainerT 类模板提供一个显式推断指示,通过显式推断指示明确模板参数 T 是 vector 类型。这是我们提供的推断指示:
template <typename U>
ContainerT(std::initializer_list<U>) -> ContainerT<std::vector<U>>;
函数形参不支持自动推导成 std::initializer_list,我们干脆写死就是 std::initializer_list,它与大括号列表是可以匹配的,相当于只需推导 std::initializer_list 的模板参数 U。当确定了 U 之后,我们希望 ContainerT 的模板参数是 std::vector,这就是这条显式推断指示的语法解释。有了这条推断指示,c2 的定义就合法了,并且也得到了我们希望的 ContainerT<std::vector> 类型。
再来看一个稍微复杂一点的例子:
template<typename T>
class Foo {
public:Foo(T value) {m_values.push_back(std::move(value));}template<class Iter>Foo(Iter begin, Iter end) {std::copy(begin, end, std::back_inserter(m_values));}
private:std::vector<T> m_values;
};Foo f1{ 5 }; // Foo<int> std::vector<int> vi{ 1, 3, 5, 7 };
Foo f2{ vi.begin(), vi.end() }; //错误
Foo 有两个构造函数,第一个构造函数配合 CTAD,使得 f1 的定义没有问题,但是 f2 的定义不被编译器支持,因为通过构造函数传递的两个迭代器,编译器无法推断出模板参数 T 的类型。当我们拿到一对迭代器的时候,我们可以通过类型萃取获得迭代器的值类型,可以将这个值类型指代类模板参数 T。
按照这个思路得到推断指示:
template<class Iter>
Foo(Iter begin, Iter end)->Foo<typename std::iterator_traits<Iter>::value_type>;
有了这个显式推断指示,上面例子代码中 f2 的定义就合法了,并且得到的 f2 的类型也是我们希望的 Foo 类型。
2.3推断指示的非典型用法
显示推断指引可以用在一些需要提供类模板特化版本的场合,比如下面这个例子中的 Foo 类模板,当面对指针类型的时候,比如字符串字面量,如果按照默认的构造函数提供的 CTAD,T 被推导为指针,成员 m_t 只保存了字符串的指针,在很多情况下,这都是比较危险的,一不小心就出现野指针访问。传统方法是针对指针类型提供特化版本,就如同这个例子一样。
template<class T>
struct Foo {Foo(T t) { m_t = t; }T m_t;
};//特化版本
template<>
struct Foo<const char*> {Foo(const char* t) { m_t = t; }std::string m_t;
};
提供特化版本也没什么不妥,就是要敲很多键盘。但是如果用显式推断指示,只需一行代码就可以了:
//推断指引
Foo(const char*)->Foo<std::string>;
少敲几次键盘,还不需要提供函数体的代码,通过类型的指示,复用原来的构造函数,有什么利用不用推断指示?
3 总结
CTAD 拖了这么长时间实在气愤,好在显式推断指示让类模板参数的自动推导比函数模板的模式匹配强大 N 倍,也就没那么大的气了。显式推断指示在标准库中也是大量引用,比如你可以这样定义一个 array:
std::array arr{1, 2, 3, 4, 5};
因为它有一条这样的推断指示:
template <class... T>
array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>;
参考资料
[1] Marc Gregoire, Professional C++ (Fifth Edition), John Wiley & Sons, Inc., 2021
[2] https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
[3] Nicolai M. Josuttis, C++20 - The Complete Guide, http://leanpub.com/cpp20’
[4] Jacek Galowicz. C++17 STL Cookbook. Packtpub. 2017
[5] P0702:Language support for Constructor Template Argument Deduction
[6] CWG 2628:Implicit deduction guides should propagate constraints
关注作者的算法专栏
https://blog.csdn.net/orbit/category_10400723.html
关注作者的出版物《算法的乐趣(第二版)》
https://www.ituring.com.cn/book/3180
相关文章:
C++ 的 CTAD 与推断指示(Deduction Guides)
1 类模板参数推导(CTAD) 1.1 曲线救国 CTAD 的全称是类模板参数推导(Class Template Argument Deduction),它允许在实例化类模板时,根据构造函数的参数类型自动推导模板参数,从而避免显式指…...

【Rust自学】13.2. 闭包 Pt.2:闭包的类型推断和标注
13.2.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发,而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中,我们会讨论 Rust 的一…...
如何将原来使用cmakelist编译的qt工程转换为可使用Visual Studio编译的项目
将原来使用CMakeLists.txt编译的Qt工程转换为可使用Visual Studio编译的项目,可以通过以下步骤实现: 一、准备阶段 安装必要的软件: 确保已安装Visual Studio,并选择了C开发相关的组件。安装CMake,并确保其版本与Qt和…...

微软确认Win10停更不碍Microsoft 365使用!未来是否更新成谜
快科技1月17日消息,微软澄清了关于Windows 10停止支持后Microsoft 365办公套件使用情况的误解。 前两天微软更新支持文档,表示2025年10月14日Windows 10停止支持之后,Microsoft 365应用程序将不再支持Windows 10设备,引发用户担忧…...

Ubuntu、Windows系统网络设置(ping通内外网)
一、 虚拟机VMware和Ubuntu系统的网络配置说明 1、虚拟机的网络适配器的模式有三种: 桥接模式NAT模式主机模式 2、虚拟机VMware的网卡配置(如何进行配置界面(虚拟机->设置)) 注意: 1、以上桥接模式(ubuntu有独立IP)、NAT模式(没有独立IP)都可以联…...
华为OD机试E卷 ---最大值
一、题目描述 给定一组整数(非负),重排顺序后输出一个最大的整数。 二、示例1 用例1 输入 10 9输出 910说明:输出结果可能非常大,所以你需要返回一个 字符串只而不是整数。 三、输入描述 数字组合 四、输出描述 最大的整数 五、解题思路 字符…...

UllnnovationHub,一个开源的WPF控件库
目录 UllnnovationHub1.项目概述2.开发环境3.使用方法4.项目简介1.WPF原生控件1.Button2.GroupBox3.TabControl4.RadioButton5.SwitchButton6.TextBox7.PasswordBox8.CheckBox9.DateTimePicker10.Expander11.Card12.ListBox13.Treeview14.Combox15.Separator16.ListView17.Data…...

Fabric区块链网络搭建:保姆级图文详解
目录 前言1、项目环境部署1.1 基础开发环境1.2 网络部署 2、后台环境2.1、环境配置2.2、运行springboot项目 3、PC端3.1、安装依赖3.2、修改区块链网络连接地址3.3、启动项目 前言 亲爱的家人们,创作很不容易,若对您有帮助的话,请点赞收藏加…...
Kubernetes (K8s) 权限管理指南
1. 引言 Kubernetes (K8s) 作为当今最流行的容器编排平台,其安全性至关重要。本指南旨在全面介绍 K8s 的权限管理机制,帮助具有一定基础的读者深入理解并掌握这一关键领域。 © ivwdcwso (ID: u012172506) 2. Kubernetes 安全模型概述 K8s 的安全模型主要包括三个阶段…...

IM聊天学习资源
文章目录 参考链接使用前端界面简单效果消息窗口平滑滚动至底部vue使用watch监听vuex中的变量变化 websocket握手认证ChatKeyCheckHandlerNettyChatServerNettyChatInitializer 参考链接 zzhua/netty-chat-web - 包括前后端 vue.js实现带表情评论功能前后端实现(仿…...

计算机视觉模型的未来:视觉语言模型
一、视觉语言模型 人工智能已经从识别数据中的简单模式跃升为理解复杂的多模态数据。该领域的发展之一是视觉语言模型 (VLM) 的兴起。这类模型将视觉和文本之间联系起来,改变了我们理解视觉数据并与之交互的方式。随着 VLM 的不断发展,它们正在为计算机视觉设定一个新的水平…...
【JAVA 基础 第(19)课】Hashtable 类用法和注意细节,是Map接口的实现类
Map接口:存放的是具有映射关系的键值对,键映射到值,键必须是唯一的 Hashtable 类,Map接口的实现类,键和值都不能为nullHashtable 是同步的,是线程安全的 public class MapTest {public static void main(String[] arg…...

浅谈 JVM
JVM 内存划分 JVM 内存划分为 四个区域,分别为 程序计数器、元数据区、栈、堆 程序计数器是记录当前指令执行到哪个地址 元数据区存储存储的是当前类加载好的数据,包括常量池和类对象的信息,.java 编译之后产生 .class 文件,运…...
html的iframe页面给帆软BI发送消息
需求:帆软的网页组件嵌套一个HTML页面,HTML页面要给帆软发消息。 解决方法是:fineReportWindow.duchamp.getWidgetByName("txt1").setValue(666); <!DOCTYPE html> <html lang"en"> <head> <…...

spark任务优化参数整理
以下参数中有sql字眼的一般只有spark-sql模块生效,如果你看过spark的源码,你会发现sql模块是在core模块上硬生生干了一层,所以反过来spark-sql可以复用core模块的配置,例外的时候会另行说明,此外由于总结这些参数是在不…...

C++ 模拟真人鼠标轨迹算法 - 防止游戏检测
一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言,原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势: 模拟…...

生产环境中常用的设计模式
生产环境中常用的设计模式 设计模式目的使用场景示例单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点- 日志记录器- 配置管理器工厂方法模式定义一个创建对象的接口,让子类决定实例化哪个类- 各种工厂类(如视频游戏工厂模式创…...

基于SpringBoot+Vue的药品管理系统【源码+文档+部署讲解】
系统介绍 基于SpringBootVue实现的药品管理系统采用前后端分离的架构方式,系统实现了用户登录、数据中心、药库管理、药房管理、物资管理、挂号管理、系统管理、基础设置等功能模块。 技术选型 开发工具:idea2020.3Webstorm2020.3 运行环境ÿ…...
【CompletableFuture实战】
CompletableFuture实战 前言 前言 过去的一年,匆匆忙忙,换了一次工作,写博客的习惯就落下了,总之,有点懈怠。希望今年能重拾信心,步入正规! CompletableFuture的用法网上资料颇多,…...
Redis 缓存穿透、击穿、雪崩 的区别与解决方案
前言 Redis 是一个高性能的键值数据库,广泛应用于缓存、会话存储、实时数据分析等场景。然而,在高并发的环境下,Redis 缓存可能会遇到 缓存击穿、缓存穿透 和 缓存雪崩 这三大问题。这些问题不仅影响系统的稳定性和性能,还经常出…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...

前端开发者常用网站
Can I use网站:一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use:Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站:MDN JavaScript权威网站:JavaScript | MDN...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...

解析“道作为序位生成器”的核心原理
解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制,重点解析"道作为序位生成器"的核心原理与实现框架: 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...