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

C++17之std::void_t

目录

1.std::void_t 的原理

2.std::void_t 的应用

2.1.判断成员存在性

2.1.1.判断嵌套类型定义

2.1.2 判断成员是否存在

2.2 判断表达式是否合法

2.2.1 判断是否支持前置++运算符

2.2.3 判断两个类型是否可做加法运算

3.std::void_t 与 std::enable_if


1.std::void_t 的原理

        std::void_t<> 是 C++ 17 标准增加的一个非常实用的功能,其实就是一个模板别名的定义,它的作用是将一系列不同的类型映射成 void 类型。在 C++ 20 的概念(concept)推出之前,它被认为是使用 SFINAE 机制的最方便的方法之一。std::void_t<> 根据对一个表达式(比如用 decltype 推断一个操作数的类型的表达式)的有效性判断,从候选集中移除某些符合条件的模板函数或模板类,从而允许特定的函数重载或类模板的特化版本成立。

        标准库中这样定义 std::void_t:

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

        std::void_t<> 定义了一个模板别名,它的作用是将模板参数列表中的 _Types 映射成 void 类型。这个别名能工作的前提是 _Types 中的每种类型都必须是合法的类型,若 _Types 中包含不合法的类型,则这个别名的定义非法,从而触发 SFINAE 机制产生相应的效果。std::void_t<> 的灵活性在于 _Types  甚至可以是 decltype() 表达式,比如 std::void_t<decltype(5)> 。这个表达式不会触发模板参数替换失败,因为 decltype(5) 推断得到 int 类型,整个表达式最终的结果相当于 void。但是 std::void_t<decltype(std::string::to_integer)> 就会失败,因为目前标准库的 std::string 没有名为 to_integer 的成员,decltype 推断不出一个合法的类型。这就是 std::void_t<>的工作原理,当 std::void_t<> 替换失败时,就会触发 SFINAE 机制删除相应的模板参数替换得到的错误结果。

2.std::void_t 的应用

2.1.判断成员存在性

2.1.1.判断嵌套类型定义

        std::void_t<> 可以用来判断一个类型 T 是否嵌套定义了某个类型。判断的原理就是利用 T::SomeType 类型的存在性,转化成 std::void_t<T::SomeType> 定义的合法性。来看下面的例子,判断某个类型是否内部嵌套定义了子类型:InnerType:

template< class, class = std::void_t<> >  //或者 template< class, class = void >
struct has_type_member : std::false_type { };template< class T >
struct has_type_member<T, std::void_t<typename T::InnerType>> : std::true_type { };// 演示代码
struct ObjectA {enum class InnerType { COLOR, SHAPE };
};
struct ObjectB {using InnerType = int;
};
struct ObjectC {
};static_assert(has_type_member<ObjectA>::value); //true, ObjectA 有 enum 类型的 InnerType 定义
static_assert(has_type_member<ObjectB>::value); //true, ObjectB 有 int 类型别名 InnerType
static_assert(has_type_member<ObjectC>::value); //false,ObjectC 没有 InnerType

        这里说明一下,因为 `std::void_t<>` 定义中的`_Types`是一个变长参数列表,可以是空,所以当 `_Types` 为空的时候也是一个合法的模板别名,即 `std::void_t<> == void`。上述代码中的 `class = std::void_t<>` 很多人也直接写成 `class = void`,效果是一样的。模板实例化过程中的匹配顺序,`void` 总是优先级最低的,所以编译器总是优先匹配第二个 `has_type_member<>` 定义(`has_type_member<>`的偏特化版本),得到`std::true_type<>`定义的 `value`。只有当 `T::InnerType` 不存在,导致`std::void_t<>` 定义失败的时候,编译器才会继续匹配第一个 `has_type_member<>` 的泛化版本。`has_type_member<>` 的泛化版本虽然因为 `void` 优先级低,总是最后才轮到,但是它能匹配任意情况,得到`std::false_type<>`定义的 `value`。根据 SFINAE 机制,编译器对之前的失败也不报错,于是 `has_type_member<>` 就完美地实现了自己的设计意图。

        `std::false_type<>` 和 `std::true_type<>` 是 `std::integral_constant<>` 的一个特化版本,`has_type_member<>`借助这个继承关系得到了一个类型为 bool 的静态变量 value。

2.1.2 判断成员是否存在

        借助于上一节的思路,判断一个类型中是否存在某个数据成员的方法非常简单,但是需要注意的是,因为`MemberName` 是数据成员的名字,所以`T::MemberName`就不是一个类型了,不能用做`std::void_t<>`的模板参数。这时候就要用到 `decltype()`了,让编译器推导出数据成员的类型:

template< class, class = void >
struct has_data_member : std::false_type { };template< class T >
struct has_data_member<T, std::void_t<decltype(T::Tom)>> : std::true_type { };

        但是对于成员函数的判断,就不能直接使用 `decltype(T::Func)`,因为这只适用于静态成员函数的情况,对于非静态的成员函数来说,T::Func 是一个非法的表达式。假设 a 是 T 类型的一个对象实例,则 `a.T::Func` 或 `a.func` 才是合法的表达式。所以对于成员函数来说,如果还是使用 `decltype(T::Func)`的语法形式,则无论的一项是否有名为 Func 的成员函数,都会因为表达式非法而替换失败,从而匹配成 false_type 的结果。难道还要构造一个 T 类型的对象才行吗?当然不是,C++ 不是还有`std::declval()`嘛。

        `std::declval() `的作用是返回任意类型 T 的右值引用类型 T&& ,可以借助这个右值引用调用 T 的成员函数,最终的效果就是在没有构造 T 的任何实例的情况下调用了 T 的成员函数,当然,这一切都是在编译期间完成的,编译器甚至都不需要函数的完整定义。具体的做法就是用`std::declval() `得到对象的右值引用,然后使用这个右值引用调用成员函数,再用`decltype()`推导函数调用返回值的类型:

std::void_t<decltype(std::declval<T>().Func())>

        如果 T 中存在名为 `Func`的成员函数,则`std::declval<T>().Func()`就是一个合法的函数调用表达式,`decltype()`就能推导出函数返回值的类型,`std::void_t<>`的模板参数就是一个合法的类型,于是它的别名定义就合法。如果 T 中不存在名为 `Func`的成员函数,则`std::declval<T>().Func()`不是一个合法的表达式,最终`std::void_t<>`的定义就是错误的,这会触发 SFINAE 机制删除错误的替换结果,从而达到选择的目的。

        根据上述分析,判断类型是否存在指定成员函数的实现可以这样做:

template<class T, class = std::void_t<>>
struct has_func_member : std::false_type {};template<class T>
struct has_func_member<T, std::void_t<decltype(std::declval<T>().fun())>> : std::true_type {};struct ObjectA {int fun() { return 42; }
};struct ObjectB {
};static_assert(has_func_member<ObjectA>::value);
static_assert(!has_func_member<ObjectB>::value);

2.2 判断表达式是否合法

        `std::void_t<>`不仅可用于判断成员的存在性,还可用来判断表达式是否合法,实际上,2.1.2 节介绍成员函数的存在性判断时,就是利用了表达式是否合法的方式处理的。这一节,我们再介绍几个这样的例子。

2.2.1 判断是否支持前置++运算符

        是的,实现思路也是尝试调用对象的前置 ++ 运算符,如果调用失败说明对象不支持前置 ++ 运算符:

template< class, class = void >
struct has_pre_increment_member : std::false_type { };template< class T >
struct has_pre_increment_member<T, std::void_t< decltype(++std::declval<T&>()) > > : std::true_type { };

        `std::declval<T&>()`得到一个右值引用,对这个右值引用调用前置 ++ 运算符,如果表达式合法,则`std::void_t<decltype(++std::declval<T&>())>`的定义就是合法的,`has_pre_increment_member<>` 的 true_type 特化版本实例化成为最佳匹配。反之,若对前置 ++ 运算符的调用失败,则`has_pre_increment_member<>`特化版本就得到一个错误的替换结果,编译器于是“不动声色地”按照`has_pre_increment_member<>`的泛化版本实例化出 false_type 的版本。

2.2.2 判断是否支持迭代器

        C++ 标准库中的容器都支持通过 begin() 和 end() 函数获得对应的迭代器,可以借助对这两个成员函数的存在性判断一个类型是否支持迭代器,当然,这不一定严谨,我们只是用这个例子展示 `std::void_t<>`的更多用法。

template <typename, typename = void>
struct is_iterable : std::false_type {};template <typename T>
struct is_iterable<T, std::void_t< decltype(std::declval<T>().begin()), decltype(std::declval<T>().end()) > > : std::true_type {};

对了,谁说 `std::void_t<>`只能用一个模板参数,这个例子不就用了两个嘛。

2.2.3 判断两个类型是否可做加法运算

        实现的思路仍然是使用`std::declval()`得到两个对象的右值引用,然后让它们“加”一下,看看“加”的表达式是否合法。

template<typename U, typename V, typename T = void> 
struct is_can_add : std::false_type  { };template<typename U, typename V>
struct is_can_add<U, V, std::void_t< decltype(std::declval<U>() + std::declval<V>()) > > : std::true_type { };

3.std::void_t 与 std::enable_if

        `std::enable_if<>` 可以用在函数返回值上,也可以用在函数参数或模板参数上,它的原理就是借助 `std::enable_if<>` 的失效条件产生错误的函数签名,然后利用 SFINAE 机制从函数候选集中移除替换失败的函数,从而达到选择特定的函数重载形式或类模板的实例化结果的目的,这是`std::enable_if<>`的使用特点。

        `std::void_t<>`则可以用来判断一个类型的正确性或存在性,借助于`decltype` 和`std::declval()`,还可以用来判断一个表达式是否合法。如果存在性和合法性判断失败将导致`std::void_t<>`的定义失败,利用这一点配合 SFINAE 机制也可以实现在编译其的一些类型选择。

        与传统的使用 SFINAE 机制的方法相比,`std::enable_if<>`和`std::void_t<>`具有更简洁的语义表达方式和更直观的语法形式,使用的方法也很方便。它们一直被认为是 C++ 20 的概念(concept)推出之前使用 SFINAE 机制的最佳方式。

推荐阅读

C++反射之检测struct或class是否实现指定函数

C++之std::declval

C++之std::enable_if

相关文章:

C++17之std::void_t

目录 1.std::void_t 的原理 2.std::void_t 的应用 2.1.判断成员存在性 2.1.1.判断嵌套类型定义 2.1.2 判断成员是否存在 2.2 判断表达式是否合法 2.2.1 判断是否支持前置运算符 2.2.3 判断两个类型是否可做加法运算 3.std::void_t 与 std::enable_if 1.std::void_t 的…...

零基础入门篇①⑥ Python可变序列类型--字典

Python从入门到精通系列专栏面向零基础以及需要进阶的读者倾心打造,9.9元订阅即可享受付费专栏权益,一个专栏带你吃透Python,专栏分为零基础入门篇、模块篇、网络爬虫篇、Web开发篇、办公自动化篇、数据分析篇…学习不断,持续更新,火热订阅中🔥专栏限时一个月(5.8~6.8)重…...

C语言面试题1-10

C语言中的内存管理及相关问题探讨 在C语言编程中&#xff0c;内存管理是一个至关重要的概念&#xff0c;掌握内存的分布及其操作不仅能够提高代码效率&#xff0c;还能避免常见的内存泄漏等问题。本文将详细介绍C语言中内存的分布、堆区和栈区的区别、标识符的命名规则、定义和…...

Qt Designer工具如何修改MainWindow窗口的标题

Qt Designer工具如何修改MainWindow窗口的标题 在MainWindow的属性编辑器中选择“windowTitle”后面一栏修改成期望的窗口标题名称即可。 按住“ctrlR”即可查看可视化界面的窗口标题...

车辆前向碰撞预警系统性能要求和测试规程

前言 本文整理《GB/T 33577-2017 智能运输系统-车辆前向碰撞预警系统性能要求和测试规程》国标文件关键信息,FCW系统性能和测试右给深层次的认识。 术语和定义 车辆前向碰撞预警系统 forward vehicle collision warning system自车 subject vehicle(SV)目标车辆 target ve…...

C#实现winform中渲染图的展示

在WinForms中实现图形的渲染展示&#xff0c;可以使用GDI绘图技术。下面是一个简单的示例&#xff0c;演示如何在WinForms中展示一个圆形图形&#xff0c;并根据用户输入的半径动态改变圆的大小&#xff1a; 请在Visual Studio中创建一个WinForms应用程序&#xff0c;并将以下…...

JTS库的讲解及使用

JTS&#xff08;Java Topology Suite&#xff09;是一套用于创建、操作和分析二维几何对象的Java库。JTS提供了丰富的几何操作和分析功能&#xff0c;是GIS&#xff08;地理信息系统&#xff09;应用中的重要工具。以下是JTS库的一些主要功能及其详细使用示例&#xff1a; 1. …...

【C++杂货铺】unordered系列容器

目录 &#x1f308; 前言&#x1f308; &#x1f4c1; unordered系列关联式容器 &#x1f4c1; 底层结构 &#x1f4c2; 哈希概念 &#x1f4c2; 哈希冲突 &#x1f4c2; 哈希函数 &#x1f4c2; 哈希冲突解决 &#x1f4c1; 模拟实现 &#x1f4c1; 总结 &#x1f308; 前…...

模板-初阶

引言&#xff1a; 在C&#xff0c;我们已经学过了函数重载&#xff0c;这使得同名函数具有多个功能。但是还有一种更省力的方法&#xff1a;采用模板。 本文主要介绍以下内容 1. 泛型编程 2. 函数模板 3. 类模板 1.泛型编程 在将这一部分之前&#xff0c;通过一个故事引…...

重载运算符C++---学习笔记

一、笔记 1. 重载运算符基础知识 重载运算符进行的运算和普通数的加减运算不同之处在于重载运算符的操作数为一个一个自定义的对象&#xff0c;所以相应的要对普通的运算符如-*%/的调用方法进行重写&#xff0c;重载的本质还是函数调用 2. 重载运算符的语法 重载运算符的语…...

SpringMVC枚举类型字段处理

在日常的项目开发中经常会遇到一些取值范围固定的字段&#xff0c;例如性别、证件类型、会员等级等&#xff0c;此时我们可以利用枚举来最大程度减少字段的乱定义&#xff0c;统一管理枚举的值。 SpringMVC中对于枚举也有默认的处理策略&#xff1a; 对于RequestParam&#xf…...

集成算法:Bagging模型、AdaBoost模型和Stacking模型

概述 目的&#xff1a;让机器学习效果更好&#xff0c;单个不行&#xff0c;集成多个 集成算法 Bagging&#xff1a;训练多个分类器取平均 f ( x ) 1 / M ∑ m 1 M f m ( x ) f(x)1/M\sum^M_{m1}{f_m(x)} f(x)1/M∑m1M​fm​(x) Boosting&#xff1a;从弱学习器开始加强&am…...

DW怎么Python:探索Dreamweaver与Python的交织世界

DW怎么Python&#xff1a;探索Dreamweaver与Python的交织世界 在数字世界的广袤天地中&#xff0c;Dreamweaver&#xff08;简称DW&#xff09;与Python这两大工具各自闪耀着独特的光芒。DW以其强大的网页设计和开发能力著称&#xff0c;而Python则以其简洁、易读和强大的编程…...

算法(十三)回溯算法---N皇后问题

文章目录 算法概念经典例子 - N皇后问题什么是N皇后问题&#xff1f;实现思路 算法概念 回溯算法是类似枚举的深度优先搜索尝试过程&#xff0c;主要是再搜索尝试中寻找问题的解&#xff0c;当发生不满足求解条件时&#xff0c;就会”回溯“返回&#xff08;也就是递归返回&am…...

论文阅读:Correcting Motion Distortion for LIDAR HD-Map Localization

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;http://arxiv.org/pdf/2308.13694.pdf 代码地址&#xff1a;https://github.com/mcdermatt/VICET 概要 激光雷达的畸变矫正是一个非常重要的工作。由于扫描式激光雷达传感器需要有限的时间来创建…...

Git操作笔记

学git已经好多次了。但是还是会忘记很多的东西&#xff0c;一些常用的操作命令和遇到的bug以后在这边记录汇总下 一.github图片展示 图片挂载&#xff0c;我是创建了一个库专门存图片&#xff0c;然后在github的md中用专用命令展示图片&#xff0c;这样你的md就不会全是文字那…...

使用Python进行数据分析的基本步骤

简介&#xff1a; 在当今的数据驱动世界中&#xff0c;数据分析已成为各行各业不可或缺的一部分。Python作为一种强大的编程语言&#xff0c;提供了丰富的库和工具&#xff0c;使得数据分析变得简单易行。本文将带你了解使用Python进行数据分析的基本步骤。 一、数据获取 从外…...

NGINX优化

NGINX优化分为两个方面&#xff1a; 一. nginx应用配置文件的优化&#xff1a; 1.nginx的性能优化: 全局块&#xff1a; 设置工作进程数&#xff1a; work_processes #设置工作进程数 设置工作进程连接数&#xff1a;work_rilmit_nofile #设置每个worker进程最大可…...

【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值

【LeetCode刷题】Day 13 题目1&#xff1a;852.山脉数组的峰顶索引思路分析&#xff1a;思路1&#xff1a;暴力枚举O(N)思路2&#xff1a;二分查找O(logN) 题目2&#xff1a;162.寻找峰值思路分析&#xff1a;思路1&#xff1a;二分查找O(logN) 题目1&#xff1a;852.山脉数组的…...

《Python学习》-- 实操篇一

一、文件操作 1. 1 读取文本文件 # 文件操作模式 # r (默认) - 只读模式。文件必须存在&#xff0c;否则会抛出FileNotFoundError。在这种模式下&#xff0c;你只能读取文件内容&#xff0c;不能写入或追加。 # w - 写入模式。如果文件存在&#xff0c;内容会被清空&#xff…...

C# 集合(二) —— List/Queue类

总目录 C# 语法总目录 集合二 List/Queue 1. List2. Queue 1. List List有ArrayList和LinkedList ArrayList 类似数组&#xff0c;查找快&#xff0c;插入删除慢(相对)LinkedList 类似双向链表&#xff0c;查找慢(相对)&#xff0c;插入删除快 //ArrayList //ArrayList Arr…...

【TB作品】MSP430 G2553 单片机口袋板,读取单片机P1.4电压显示,ADC

功能 读取P1.4电压&#xff0c;显示到口袋板显示屏&#xff0c;电压越高亮灯越多。 部分程序 while (1){ADC10CTL0 | ENC ADC10SC; // Sampling and conversion startLPM0;adcvalue ADC10MEM; //原始数据 0到1023adtest (float) adcvalue / 1024.…...

知乎x-zse-96、x-zse-81

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章未…...

【Linux】Linux工具——yum,vim

1.Linux 软件包管理器——yum Linux安装软件&#xff1a; 源代码安装&#xff08;不建议&#xff09;rpm安装&#xff08;类似Linux安装包&#xff0c;版本可能不兼容&#xff0c;不推荐&#xff0c;容易报错&#xff09;yum安装&#xff08;解决了安装源&#xff0c;安装版本&…...

ES 生命周期管理

一 .概念 ILM定义了四个生命周期阶段&#xff1a;Hot&#xff1a;正在积极地更新和查询索引。Warm&#xff1a;不再更新索引&#xff0c;但仍在查询。cold&#xff1a;不再更新索引&#xff0c;很少查询。信息仍然需要可搜索&#xff0c;但是如果这些查询速度较慢也可以。Dele…...

【JavaScript脚本宇宙】揭秘HTTP请求库:深入理解它们的特性与应用

深度揭秘&#xff1a;六大HTTP请求库的比较与应用 前言 在这篇文章中&#xff0c;我们将探讨六种主要的HTTP请求库。这些库为处理网络请求提供了不同的工具和功能&#xff0c;包括Axios、Fetch API、Request、SuperAgent、Got和Node-fetch。通过本文&#xff0c;你将对每个库…...

【强化学习】DPO(Direct Preference Optimization)算法学习笔记

【强化学习】DPO&#xff08;Direct Preference Optimization&#xff09;算法学习笔记 RLHF与DPO的关系KL散度Bradley-Terry模型DPO算法流程参考文献 RLHF与DPO的关系 DPO&#xff08;Direct Preference Optimization&#xff09;和RLHF&#xff08;Reinforcement Learning f…...

vue3 todolist 简单例子

vue3 简单的TodList 地址&#xff1a; https://gitee.com/cheng_yong_xu/vue3-composition-api-todo-app-my 效果 step-1 初始化项项目 我们不采用vue cli 搭建项目 直接将上图文件夹&#xff0c;复制到vscode编辑器&#xff0c;清空App.vue的内容 安装包 # 安装包 npm…...

Linux项目编程必备武器!

本文目录 一、更换源服务器二、下载man开发手册(一般都自带&#xff0c;没有的话使用下面方法下载) 一、更换源服务器 我们使用apt-get等下载命令下载的软件都是从源服务器上获取的&#xff0c;有些软件包在某个服务器上存在&#xff0c;而另一个服务器不存在。所以我们可以添加…...

AndroidStudio编译很慢问题解决

如果gradle同步、编译下载很慢&#xff0c;可以换一下仓库阿里云镜像 repositories {maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/jcenter } maven { url https://maven.aliyun.com/repository/public } goog…...