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

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 类模板参数推导&#xff08;CTAD&#xff09; 1.1 曲线救国 ​ CTAD 的全称是类模板参数推导&#xff08;Class Template Argument Deduction&#xff09;&#xff0c;它允许在实例化类模板时&#xff0c;根据构造函数的参数类型自动推导模板参数&#xff0c;从而避免显式指…...

【Rust自学】13.2. 闭包 Pt.2:闭包的类型推断和标注

13.2.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发&#xff0c;而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中&#xff0c;我们会讨论 Rust 的一…...

如何将原来使用cmakelist编译的qt工程转换为可使用Visual Studio编译的项目

将原来使用CMakeLists.txt编译的Qt工程转换为可使用Visual Studio编译的项目&#xff0c;可以通过以下步骤实现&#xff1a; 一、准备阶段 安装必要的软件&#xff1a; 确保已安装Visual Studio&#xff0c;并选择了C开发相关的组件。安装CMake&#xff0c;并确保其版本与Qt和…...

微软确认Win10停更不碍Microsoft 365使用!未来是否更新成谜

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

Ubuntu、Windows系统网络设置(ping通内外网)

一、 虚拟机VMware和Ubuntu系统的网络配置说明 1、虚拟机的网络适配器的模式有三种&#xff1a; 桥接模式NAT模式主机模式 2、虚拟机VMware的网卡配置(如何进行配置界面(虚拟机->设置)) 注意&#xff1a; 1、以上桥接模式(ubuntu有独立IP)、NAT模式(没有独立IP)都可以联…...

华为OD机试E卷 ---最大值

一、题目描述 给定一组整数(非负)&#xff0c;重排顺序后输出一个最大的整数。 二、示例1 用例1 输入 10 9输出 910说明:输出结果可能非常大&#xff0c;所以你需要返回一个 字符串只而不是整数。 三、输入描述 数字组合 四、输出描述 最大的整数 五、解题思路 字符…...

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、启动项目 前言 亲爱的家人们&#xff0c;创作很不容易&#xff0c;若对您有帮助的话&#xff0c;请点赞收藏加…...

Kubernetes (K8s) 权限管理指南

1. 引言 Kubernetes (K8s) 作为当今最流行的容器编排平台,其安全性至关重要。本指南旨在全面介绍 K8s 的权限管理机制,帮助具有一定基础的读者深入理解并掌握这一关键领域。 © ivwdcwso (ID: u012172506) 2. Kubernetes 安全模型概述 K8s 的安全模型主要包括三个阶段…...

IM聊天学习资源

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

计算机视觉模型的未来:视觉语言模型

一、视觉语言模型 人工智能已经从识别数据中的简单模式跃升为理解复杂的多模态数据。该领域的发展之一是视觉语言模型 (VLM) 的兴起。这类模型将视觉和文本之间联系起来,改变了我们理解视觉数据并与之交互的方式。随着 VLM 的不断发展,它们正在为计算机视觉设定一个新的水平…...

【JAVA 基础 第(19)课】Hashtable 类用法和注意细节,是Map接口的实现类

Map接口&#xff1a;存放的是具有映射关系的键值对&#xff0c;键映射到值&#xff0c;键必须是唯一的 Hashtable 类&#xff0c;Map接口的实现类,键和值都不能为nullHashtable 是同步的&#xff0c;是线程安全的 public class MapTest {public static void main(String[] arg…...

浅谈 JVM

JVM 内存划分 JVM 内存划分为 四个区域&#xff0c;分别为 程序计数器、元数据区、栈、堆 程序计数器是记录当前指令执行到哪个地址 元数据区存储存储的是当前类加载好的数据&#xff0c;包括常量池和类对象的信息&#xff0c;.java 编译之后产生 .class 文件&#xff0c;运…...

html的iframe页面给帆软BI发送消息

需求&#xff1a;帆软的网页组件嵌套一个HTML页面&#xff0c;HTML页面要给帆软发消息。 解决方法是&#xff1a;fineReportWindow.duchamp.getWidgetByName("txt1").setValue(666); <!DOCTYPE html> <html lang"en"> <head> <…...

spark任务优化参数整理

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

C++ 模拟真人鼠标轨迹算法 - 防止游戏检测

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

生产环境中常用的设计模式

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

基于SpringBoot+Vue的药品管理系统【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的药品管理系统采用前后端分离的架构方式&#xff0c;系统实现了用户登录、数据中心、药库管理、药房管理、物资管理、挂号管理、系统管理、基础设置等功能模块。 技术选型 开发工具&#xff1a;idea2020.3Webstorm2020.3 运行环境&#xff…...

【CompletableFuture实战】

CompletableFuture实战 前言 前言 过去的一年&#xff0c;匆匆忙忙&#xff0c;换了一次工作&#xff0c;写博客的习惯就落下了&#xff0c;总之&#xff0c;有点懈怠。希望今年能重拾信心&#xff0c;步入正规&#xff01; CompletableFuture的用法网上资料颇多&#xff0c;…...

Redis 缓存穿透、击穿、雪崩 的区别与解决方案

前言 Redis 是一个高性能的键值数据库&#xff0c;广泛应用于缓存、会话存储、实时数据分析等场景。然而&#xff0c;在高并发的环境下&#xff0c;Redis 缓存可能会遇到 缓存击穿、缓存穿透 和 缓存雪崩 这三大问题。这些问题不仅影响系统的稳定性和性能&#xff0c;还经常出…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...