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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/66344.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell正则表达式与文本处理三剑客(grep、sed、awk)

一、正则表达式 Shell正则表达式分为两种&#xff1a; 基础正则表达式&#xff1a;BRE&#xff08;basic regular express&#xff09; 扩展正则表达式&#xff1a;ERE&#xff08;extend regular express&#xff09;&#xff0c;扩展的表达式有、?、|和() 1.1 基本正则表…

arcgis提取不规则栅格数据的矢量边界

效果 1、准备数据 栅格数据:dem或者dsm 2、栅格重分类 分成两类即可 3、新建线面图层 在目录下选择预先准备好的文件夹,点击右键,选择“新建”→“Shapefile”,新建一个Shapefile文件。 在弹出的“新建Shapefile”对话框内“名称”命名为“折线”,“要素类型”选…

阿里云通义实验室自然语言处理方向负责人黄非:通义灵码2.0,迈入 Agentic AI

通义灵码是基于阿里巴巴通义大模型研发的AI 智能编码助手&#xff0c;在通义灵码 1.0 时代&#xff0c;我们针对代码的生成、补全和问答&#xff0c;通过高效果、低时延&#xff0c;研发出了国内最受欢迎的编码助手。 在通义灵码 2.0 发布会上&#xff0c;阿里云通义实验室自然…

Open3D 最小二乘拟合平面(直接求解法)【2025最新版】

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 博客长期更新,本文最近更新时间为:2025年1月18日。 一、算法原理 平面方程的一般表达式为:

超标量处理器设计2-cache

1. cache 介绍 影响Cache缺失的情况有3种&#xff1a; Compulsory: 第一次被访问的指令或者数据肯定不会在cache中&#xff0c;需要通过预取来减少这种缺失Capcity: Cache容量越大&#xff0c;缺失就可以更少, 程序频繁使用的三个数据来源于3个set&#xff0c; 但是&#xff…

当PHP遇上区块链:一场奇妙的技术之旅

PHP 与区块链的邂逅 在技术的广袤宇宙中&#xff0c;区块链技术如同一颗耀眼的新星&#xff0c;以其去中心化、不可篡改、透明等特性&#xff0c;掀起了一场席卷全球的变革浪潮。众多开发者怀揣着对新技术的热忱与探索精神&#xff0c;纷纷投身于区块链开发的领域&#xff0c;试…

vscode的安装与使用

下载 地址&#xff1a;https://code.visualstudio.com/ 安装 修改安装路径&#xff08;不要有中文&#xff09; 点击下一步&#xff0c;创建桌面快捷方式&#xff0c;等待安装 安装中文插件 可以根据自己的需要安装python和Jupyter插件

浅谈云计算19 | OpenStack管理模块 (上)

OpenStack管理模块&#xff08;上&#xff09; 一、操作界面管理架构二、认证管理2.1 定义与作用2.2 认证原理与流程2.2.1 认证机制原理2.2.2 用户认证流程 三、镜像管理3.1 定义与功能3.2 镜像服务架构3.3 工作原理与流程3.3.1 镜像存储原理3.3.2 镜像检索流程 四、计算管理4.…

彩色图像面积计算一般方法及MATLAB实现

一、引言 在数字图像处理中&#xff0c;经常需要获取感兴趣区域的面积属性&#xff0c;下面给出图像处理的一般步骤。 1.读入的彩色图像 2.将彩色图像转化为灰度图像 3.灰度图像转化为二值图像 4.区域标记 5.对每个区域的面积进行计算和显示 二、程序代码 %面积计算 cle…

分布式理解

分布式 如何理解分布式 狭义的分布是指&#xff0c;指多台PC在地理位置上分布在不同的地方。 分布式系统 分布式系**统&#xff1a;**多个能独立运行的计算机&#xff08;称为结点&#xff09;组成。各个结点利用计算机网络进行信息传递&#xff0c;从而实现共同的“目标或者任…

Red Hat8:搭建FTP服务器

目录 一、匿名FTP访问 1、新建挂载文件 2、挂载 3、关闭防火墙 4、搭建yum源 5、安装VSFTPD 6、 打开配置文件 7、设置配置文件如下几个参数 8、重启vsftpd服务 9、进入图形化界面配置网络 10、查看IP地址 11、安装ftp服务 12、遇到拒绝连接 13、测试 二、本地…

Re78 读论文:GPT-4 Technical Report

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文全名&#xff1a;GPT-4 Technical Report 官方博客&#xff1a;GPT-4 | OpenAI appendix懒得看了。 文章目录 1. 模型训练过程心得2. scaling law3. 实验结果减少风险 1. 模型训练过程心得 模型结构还…

推荐单通道有刷直流电机驱动芯片AT8236

单通道直流有刷电机驱动芯片AT8236 描述应用特点型号选择典型应用原理图管脚列表推荐工作条件 atT A 25C电气特性 atT A 25C,V M 24VH桥控制电流控制死区时间休眠模式过流保护 (OCP)过温保护 (TSD)欠压锁定保护(UVLO) PCB 版图建议典型应用示例 描述 AT8236是一款直流有刷电机…

聚铭网络6款产品入选CCIA《网络安全专用产品指南》

近日&#xff0c;中国网络安全产业联盟CCIA正式发布《网络安全专用产品指南》&#xff08;第二版&#xff09;&#xff08;以下简称《指南》&#xff09;。聚铭网络凭借突出技术优势、创新能力以及市场积累&#xff0c;旗下安全产品成功入选防火墙、网络安全审计、日志分析、网…

将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(1)

问题 项目里使用了 AzureBlob 存储了用户上传的各种资源文件&#xff0c;近期 AzureBlob 的流量费用增长很快&#xff0c;想通过分析Blob的日志&#xff0c;获取一些可用的信息&#xff0c;所以有了这个需求&#xff1a;将存储账户的日志&#xff08;读写&#xff0c;审计&…

ESP32S3基于espidf接入网络获取NTP时间

ESP32S3基于espidf接入网络获取NTP时间 &#x1f4cc; 相关篇《ESP32S3基于espidf接入网络配置介绍》&#x1f4cd;官方相关SNTP 时间同步介绍文档&#xff1a;https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-reference/system/system_time.html?highli…

【蓝桥杯选拔赛真题63】C++奇数 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解

目录 C++奇数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 七、推荐资料 C++奇数 第十四届蓝桥杯青少年创意编程大赛C++选拔赛真题 一、题目要求 1、编程实现 给定两个正整数N和M(10≤N<M≤10000),请找出N到M…

(学习总结20)C++11 可变参数模版、lambda表达式、包装器与部分新内容添加

C11 可变参数模版、lambda表达式、包装器与部分新内容添加 一、可变参数模版基本语法及原理包扩展emplace系列接口 二、lambda表达式lambda表达式语法捕捉列表lambda的原理lambda的应用 三、包装器bindfunction 四、部分新内容添加新的类功能1.默认的移动构造和移动赋值2.声明时…

东芝e-STUDIO2829A复印机提示“维护”该如何操作

东芝e-STUDIO2829A复印机基本参数: 产品类型 数码复合机 颜色类型 黑白 涵盖功能 复印/打印/扫描 最大原稿尺寸 A3 处 理 器 500MHz 内存容量 标配:512MB,选配:1GB 供纸容量 标配纸盒:350页(A4),最大容…

春秋杯-WEB

SSTI 可以看到主页那里有个登录测试之后为ssti {{4*4}} fenjing梭哈即可得到payload {{((g.pop.__globals__.__builtins__.__import__(os)).popen(cat flag)).read()}}file_copy 看到题目名字为file_copy&#xff0c; 当输入路径时会返回目标文件的大小&#xff0c; 通…