掌握现代C++的模板元编程类型检测技术

最近写代码恰好用到了C++模板元编程的类型检测能力,以前对其原理有个大概的印象,但随着C++11/C++17等新特性的加入,很多做法和以前不同了,借此机会重新梳理一下这方面的知识点。

void_t 的引入

在 C++17 之前,模板编程中通常需要编写复杂的部分特化和重载来检测类型特征。C++17 引入了 std::void_t,简化了这一过程。其定义如下:

template< class... >
using void_t = void;

这个定义看似简单,但实际上它为模板编程打开了新的可能性。

void_t 在 SFINAE 中的应用

SFINAE是Substitution Failure Is Not An Error的缩写,直译为:匹配失败不是错误。属于C++模板编程中的高级技巧,但属于模板元编程中的基本技巧。SFINAE 是一种模板实例化过程中的规则,当模板参数替换失败时,并不会产生编译错误,而是导致模板实例被丢弃,编译器会继续寻找其他匹配的模板实例。

使用 void_t,可以创建一些检测类型特征的工具,例如:

检测类型成员

我们可以编写一个模板结构体来检测一个类型是否包含某个成员类型 type

template <class, class = std::void_t<>>
struct has_type : std::false_type {};template <class T>
struct has_type<T, std::void_t<typename T::type>> : std::true_type {};
检测成员变量

同样的技巧可以用来检查一个类型是否有某个特定的成员变量 a

template <class, class = std::void_t<>>
struct has_a_member : std::false_type {};template <class T>
struct has_a_member<T, std::void_t<decltype(std::declval<T>().a)>> : std::true_type {};
检测迭代器

我们还可以检验一个类型是否可迭代,即是否有 begin()end() 方法:

template <typename, typename = void>
constexpr bool is_iterable = false;template <typename T>
constexpr bool is_iterable<T, std::void_t<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>> = true;
检测成员函数

同样可以检查一个类型是否有某个特定的成员函数 hello

template <class T, class = void>
struct has_hello_func : std::false_type {};template <class T>
struct has_hello_func<T, std::void_t<decltype(std::declval<T>().hello())>> : std::true_type {};

std::declval的作用

std::declval 是一个在 <utility> 头文件中定义的函数模板,它的主要作用是在不实例化对象的情况下获取该类型的引用,以便在编译时期在表达式中使用。这在模板元编程和类型萃取中特别有用,因为它允许我们对某个类型的成员进行操作,而不需要构造实际的对象。

std::declval 通常与 decltype 结合使用,用于推导表达式的类型。它只能在不被求值的上下文中使用(如 decltypesizeof 中),因为它实际上没有定义,只是一个声明。如果在运行时尝试使用 std::declval,将会导致链接错误。

让我们来仔细解释上面出现过的这段代码:

template <class T, class = void>
struct has_hello_func : std::false_type {};

这里定义了一个模板结构体 has_hello_func,它默认继承自 std::false_type。这个结构体的作用是用于检查类型 T 是否有成员函数 hello。这里使用了一个非类型模板参数,其默认值是 void。这是为了利用 SFINAE 规则准备的,如果 T 不满足某些条件,这个基础版本将会被选择。

template <class T>
struct has_hello_func<T, std::void_t<decltype(std::declval<T>().hello())>> : std::true_type {};

这是 has_hello_func 的一个特化版本。它只会在模板参数 T 满足 decltype(std::declval<T>().hello()) 是一个有效表达式的情况下实例化。这个表达式的作用是尝试调用类型 Thello 成员函数,而不实际构造一个 T 的实例。如果该成员函数存在,decltype 将成功推导出其类型,并且 std::void_t<decltype(...)> 将等价于 void,从而使得这个特化版本满足 SFINAE 条件,成为被选择的模板。

如果 T 有成员函数 hello,那么 std::void_t<decltype(std::declval<T>().hello())> 就不会导致替换失败,这个特化版本会被实例化,结构体将从 std::true_type 继承,其 value 成员将是 true。如果 T 没有 hello 函数,那么表达式 std::declval<T>().hello() 会导致替换失败,因此基础版本(继承自 std::false_type)将会被选择,其 value 成员将是 false

C++ 20的做法

随着 C++20 标准的推出,类型检测在 C++ 中进入了一个新的时代。C++20 引入了两个关键特性:Constraints(约束)和 Concepts(概念),它们为类型检测提供了官方的语言支持,极大地简化了模板编程。

Concepts

Concepts 是对模板参数所需特性的正式规定。它们是可编译的规范,定义了类型必须满足的接口和语义要求。Concepts 允许开发者以声明性的方式指定模板参数应该遵循的约束,这使得模板代码更加清晰和容易理解。使用 Concepts,编译器可以提供更清晰的错误信息,因为它可以检查类型是否符合概念的要求,并在不符合时报错。

例如,如果你想要定义一个只接受迭代器类型的模板函数,你可以这样做:

#include <concepts>template <typename T>
requires std::input_iterator<T>
void myFunction(T iter) {// ...
}

或者使用新的语法糖来简化:

template <std::input_iterator T>
void myFunction(T iter) {// ...
}

Constraints

Constraints 是概念的实际表达式,它们是概念要求的具体化。Constraints 可以用来指定模板参数必须符合的条件。它们可以是简单的表达式也可以是复杂的布尔逻辑。C++20 中的 requires 表达式用来指定 constraints,提供了一种更简洁和灵活的方式来指定模板参数的要求。

例如,可以使用 requires 表达式来直接在模板参数列表中对类型进行约束:

template <typename T>
requires std::integral<T>
T add(T a, T b) {return a + b;
}

这个函数 add 要求模板参数 T 必须是一个整数类型(满足 std::integral 的概念)。

C++20 概念的好处

  • 更清晰的代码:概念使得模板的意图更加明确,代码可读性大大提高。
  • 更好的编译器诊断:当类型不满足模板要求时,编译器可以提供更具体和有用的错误信息。
  • 更好的性能:在某些情况下,因为类型检测更加精确,编译器可以生成更优化的代码。
  • 更简单的类型检测:不再需要编写繁琐的 std::void_t 类型特征模板来检查类型属性,概念本身就定义了类型应该具备的属性。

C++20 的 Constraints 和 Concepts 出现后,开发者可以利用标准库提供的预定义概念,或者定义自己的概念,以更自然、清晰和直观的方式来编写模板代码。

结语

std::void_t 的引入极大简化了模板元编程中的类型检测,通过利用 SFINAE 原则和模板特化优先级,可以编写出既简洁又强大的类型特征检查工具。C++发展到C++20之后,检查类型约束的清晰度和灵活性大大增加,但是目前大部分模板库主流还是使用SFINAE相关技术,也许再过段时间C++20的Constraints(约束)和 Concepts(概念)才会普及。

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

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

相关文章

利氪科技拿下C轮超级融资,国产智能底盘黑马奔向黄金时代

“智能驾驶遗珠&#xff0c;国产替代富矿。” 这是海通证券在最近一期研报中&#xff0c;描述线控底盘产业的用语。它很巧妙地点明了&#xff0c;这个藏在车身之下的部分&#xff0c;拥有何种特征——稳坐技术体系的核心点位&#xff0c;拥有前景广阔的市场。 事实上&#xf…

mysql、mariadb 登录主机的含义,如何修改登录主机,如何删除登录主机

MariaDB版本: 10.3.39 登录主机的含义&#xff1a; 参考 1 阿风说事&#xff1a;说世间百态、聊奇闻趣事&#xff0c;分享个人观点和独到见解 2 mysql授权localhost&%区别及一直授权错误解决办法&#xff08;安装openstack有感&#xff09; 3 ERROR 1396 (HY000): Operat…

为什么要学习PMP

学习PMP&#xff08;项目管理专业人士认证&#xff09;能够在职场竞争力、薪资待遇、项目管理技能等方面带来显著的提升。以下是学习PMP的具体分析&#xff1a; 1、职场竞争力 升职加薪&#xff1a;学习PMP能够提升个人在项目中的管理能力和解决问题的能力&#xff0c;从而在…

一问搞懂Linux信号【上】

Linux信号在Linux系统中的地位仅此于进程间通信&#xff0c;其重要程度不言而喻。本文我们将从信号产生&#xff0c;信号保存&#xff0c;信号处理三个方面来讲解信号。 &#x1f6a9;结合现实认识信号 在讲解信号产生之前&#xff0c;我们先做些预备的工作。 现实生活中信号…

vue3-openlayers 轨迹回放(历史轨迹),实时轨迹

vue3-openlayers 轨迹回放&#xff08;历史轨迹&#xff09;&#xff0c;实时轨迹 本篇介绍一下使用vue3-openlayers轨迹回放&#xff08;历史轨迹&#xff09;&#xff0c;实时轨迹 1 需求 轨迹回放&#xff08;历史轨迹&#xff09;实时轨迹 2 分析 可以使用和上一篇相同…

编译原理-各章典型题型+思路求解

第2章文法和语言习题 基础知识&#xff1a; 思路&#xff1a; 基础知识&#xff1a; 思路&#xff1a; 基础知识&#xff1a; 编译原理之 短语&直接短语&句柄 定义与区分_编译原理短语,直接短语,句柄-CSDN博客 思路&#xff1a; 题目&#xff1a; 基础解释&#xff1a…

【PID _stm32 教程】

【PID电机速度闭环控制-PID算法(章节:8.3-PID算法初步体验与算法理解)】 https://www.bilibili.com/video/BV1q341197kn

关于使用tensorflow_gpu遇到的问题

前言 我使用的是tensorflow_gpu2.6与python3.9&#xff0c;还要下载cuda与cudnn。 numpy版本问题 AttributeError: module numpy has no attribute object. np.object was a deprecated alias for the builtin object. To avoid this error in existing code, use object by i…

一种快速设计PCB外壳的方法

设计PCB外壳比较好用的工具是SW但是有时候需要快速设计外壳的情况下使用立创EDA的外壳设计功能很好用&#xff0c;设计完成之后可以直接导出STL文件&#xff1a; 可以看到设计的外壳还是蛮精美的&#xff1a; 特别注意&#xff0c;设计外壳的时候要考虑如何把PCB放进壳子中&…

[Day 17] 區塊鏈與人工智能的聯動應用:理論、技術與實踐

區塊鏈在金融業的應用 前言 區塊鏈技術作為一種去中心化的分佈式賬本技術&#xff0c;自其誕生以來便展示出極大的潛力&#xff0c;特別是在金融領域。區塊鏈技術可以通過提供透明性、安全性和效率來改變金融業的運作方式。在本文中&#xff0c;我們將深入探討區塊鏈在金融業…

【文心智能体大赛】迎接属于你的休闲娱乐导师!

迎接属于你的休闲娱乐导师&#xff01; 前言创建智能体发布智能体最后结语 前言 文心智能体平台AgentBuilder 是百度推出的基于文心大模型的智能体&#xff08;Agent&#xff09;平台&#xff0c;支持广大开发者根据自身行业领域、应用场景&#xff0c;选取不同类型的开发方式&…

【秋招刷题打卡】Day01-自定义排序

Day01-自定排序 前言 给大家推荐一下咱们的 陪伴打卡小屋 知识星球啦&#xff0c;详细介绍 >笔试刷题陪伴小屋-打卡赢价值丰厚奖励 < ⏰小屋将在每日上午发放打卡题目&#xff0c;包括&#xff1a; 一道该算法的模版题 (主要以力扣&#xff0c;牛客&#xff0c;acwin…

EulerOS 安装docker 拉取opengauss 、redis镜像

#下载docker包 wget https://download.docker.com/linux/static/stable/x86_64/docker-18.09.9.tgz #解压 tar zxf docker-18.09.9.tgz #移动解压后的文件夹到/usr/bin mv docker/* /usr/bin #写入docker.service cat >/usr/lib/systemd/system/docker.service <<E…

通过 Setapp 使用 240 多款 Mac 生产力工具以及 GPT-4o

Setapp 是一项革命性的订阅服务&#xff0c;可以使用 240 多款 Mac 应用程序的综合套件&#xff0c;并配有强大的人工智能助手。 通过 Setapp 为你的工作效率和生产力增添魔力。 Setapp 官网&#xff1a;访问&#xff08;提供 7 天试用&#xff09; Setapp 的主要功能 AI 助手…

Spring Boot中的各种事件

spring boot 各种事件贯穿整个启动的生命周期&#xff0c;读懂了这些事件也差不多理解了springboot的启动流程。 SpringApplicationRunListener中的事件 接口org.springframework.boot.SpringApplicationRunListener定义了spring启动过程中各个事件被触发的顶层方法 public …

WPF文本框中加提示语

效果&#xff1a; WPF中貌似不能像winfrom里一样直接加提示语&#xff0c;需要使用TextBox.Style&#xff0c;将Trigger标签插入进去。 贴源码&#xff1a; <WrapPanel Name"TakeOverExpressNo1"><Label Content"物流单号&#xff1a;"><…

VSCode中全局搜索和替换的快捷键是什么?

在 Visual Studio Code (VSCode) 中进行全局搜索和替换的快捷键是&#xff1a; 全局搜索&#xff1a; 使用快捷键 CtrlShiftF&#xff08;在 Windows 和 Linux 上&#xff09;或 CmdShiftF&#xff08;在 macOS 上&#xff09;可以打开全局搜索功能&#xff0c;可以在整个工作…

oracle12c到19c adg搭建(六)切换后12c备库服务器安装19c软件在19c主库升级数据字典后尝试同步

一、安装19c软件 参考文章oracle12c到19c adg搭建&#xff08;三&#xff09;oracle19c数据库软件安装 二、原主库尝试通过19c软件启动数据库 2.1复制12c的相关参数文件和密码文件到19c目录 注意:密码文件需要从已切换主库19c传过来 [oracleo12u19p ~]$ cd /u01/app/oracle…

labelme 标注岩石薄片数据集流程

labelme 数据标注使用流程 1.打开anaconda环境2.打开labelme工具3.打开数据集文件夹4.开始标注5. 标注完成6. 修改labels.txt文件7. 将标注结果可视化8. 完成json转图片9. 全部命令总结 1.打开anaconda环境 2.打开labelme工具 输入下列两条命令&#xff0c;打开labelme工具 &a…

Vue的学习之安装Vue

目录 一、Vue的特点 二、Vue的学习 一、Vue的特点 1.采用组件化模式&#xff08;xxx.vue包含htmlcssjs&#xff09; 2.声明式编码&#xff0c;编码人员无需直接操作DOM&#xff0c;提高开发效率 3.使用虚拟DOM优秀的DIFF算法&#xff08;DIFF是用于新旧虚拟DOM的比较&#…