C++之std::decay

1.简介

std::decay是C++11之后引进的模板编程工具,它的主要作用是将给定的类型T转换为它的“衰变”类型。这个“衰变”类型是指去除类型T的所有引用、常量和易变性限定符,以及将所有数组和函数转换为对应的指针类型后得到的类型;在头文件 <type_traits> 中定义:

template< class T >
struct decay;

2.辅助类

2.1.std::remove_reference和std::remove_reference_t

先看实现:

template <class _Ty>
struct remove_reference {using type                 = _Ty;using _Const_thru_ref_type = const _Ty;
};template <class _Ty>
struct remove_reference<_Ty&> {       //左值引用using type                 = _Ty;using _Const_thru_ref_type = const _Ty&;
};template <class _Ty>
struct remove_reference<_Ty&&> {       //右值引用using type                 = _Ty;using _Const_thru_ref_type = const _Ty&&;
};template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

从上面的代码实现来看,它的作用就是擦除类型引用,无论是左值引用还是右值引用,例如,如果_Ty是int&int&&,那么std::remove_reference_t<_Ty>就是int。示例如下:

#include <iostream> // std::cout
#include <type_traits> // std::is_sametemplate<class T1, class T2>
void print_is_same() {std::cout << std::is_same<T1, T2>() << '\n';
}int main() {std::cout << std::boolalpha;print_is_same<int, int>();   //输出: trueprint_is_same<int, int &>();  //输出: falseprint_is_same<int, int &&>();  //输出: falseprint_is_same<int, std::remove_reference<int>::type>(); //输出: trueprint_is_same<int, std::remove_reference<int &>::type>(); //输出: trueprint_is_same<int, std::remove_reference<int &&>::type>(); //输出: true
}

2.2.std::remove_cv

用于擦除类型的const和volatile限定符,返回的是一个去除了const和volatile限定符的类型,例如,如果T是const intvolatile int,那么std::remove_cv_t<T>就是int它的实现如下:

template <class _Ty>
struct remove_cv { // remove top-level const and volatile qualifiersusing type = _Ty;template <template <class> class _Fn>using _Apply = _Fn<_Ty>; // apply cv-qualifiers from the class template argument to _Fn<_Ty>
};template <class _Ty>
struct remove_cv<const _Ty> {    //擦除constusing type = _Ty;template <template <class> class _Fn>using _Apply = const _Fn<_Ty>;
};template <class _Ty>
struct remove_cv<volatile _Ty> {  //擦除volatile using type = _Ty;template <template <class> class _Fn>using _Apply = volatile _Fn<_Ty>;
};template <class _Ty>
struct remove_cv<const volatile _Ty> { //擦除const volatileusing type = _Ty;template <template <class> class _Fn>using _Apply = const volatile _Fn<_Ty>;
};template <class _Ty>
using remove_cv_t = typename remove_cv<_Ty>::type;

示例如下:

#include <iostream>
#include <type_traits>int main() {typedef std::remove_cv<const int>::type type1;typedef std::remove_cv<volatile int>::type type2;typedef std::remove_cv<const volatile int>::type type3;typedef std::remove_cv<const volatile int*>::type type4;typedef std::remove_cv<int * const volatile>::type type5;std::cout << "test1 " << (std::is_same<int, type1>::value? "passed" : "failed") << '\n';std::cout << "test2 " << (std::is_same<int, type2>::value? "passed" : "failed") << '\n';std::cout << "test3 " << (std::is_same<int, type3>::value? "passed" : "failed") << '\n';std::cout << "test4 " << (std::is_same<const volatile int*, type4>::value? "passed" : "failed") << '\n';std::cout << "test5 " << (std::is_same<int*, type5>::value? "passed" : "failed") << '\n';
}

输出:

test1 passed
test2 passed
test3 passed
test4 passed
test5 passed

2.3.std::is_array

template< class T >
struct is_array;

主要是判断 T 是否为数组类型,如果 T 为数组类型,那么成员常量 value 等于 true。对于其它类型,value 等于 false,  它的实现如下:

template <class>
_INLINE_VAR constexpr bool is_array_v = false; // determine whether type argument is an arraytemplate <class _Ty, size_t _Nx>
_INLINE_VAR constexpr bool is_array_v<_Ty[_Nx]> = true;template <class _Ty>
_INLINE_VAR constexpr bool is_array_v<_Ty[]> = true;template <class _Ty>
struct is_array : bool_constant<is_array_v<_Ty>> {};

从源码很清晰的看到当输入为数组类型的时候,特例化返回true。示例如下:

#include <array>
#include <iostream>
#include <type_traits>class A {};int main() 
{std::cout << std::boolalpha;std::cout << std::is_array<A>::value << '\n';  //输出: falsestd::cout << std::is_array<A[]>::value << '\n'; //输出: truestd::cout << std::is_array<A[3]>::value << '\n'; //输出: truestd::cout << std::is_array<float>::value << '\n';  //输出: falsestd::cout << std::is_array<int>::value << '\n';   //输出: falsestd::cout << std::is_array<int[]>::value << '\n';  //输出: truestd::cout << std::is_array<int[3]>::value << '\n';  //输出: true std::cout << std::is_array<std::array<int, 3>>::value << '\n'; //输出: false
}

2.4.std::is_function

std::is_function(C++11)用于检查类型是否为函数,注意此处函数类型不包括std::function, lambda, 有重载operator()的类,它的实现如下:

template <class _Ty>
_INLINE_VAR constexpr bool is_function_v = // only function types and reference types can't be const qualified!is_const_v<const _Ty> && !is_reference_v<_Ty>;template <class _Ty>
struct is_function : bool_constant<is_function_v<_Ty>> {};

示例如下:

#include <iostream>
#include <type_traits>struct A {int fun() const&;
};template<typename>
struct PM_traits {};template<class T, class U>
struct PM_traits<U T::*> {using member_type = U;
};int f();int main() 
{std::cout << std::boolalpha;std::cout << std::is_function<A>::value << '\n';   //输出 : falsestd::cout << std::is_function<int(int)>::value << '\n';  //输出 : truestd::cout << std::is_function<decltype(f)>::value << '\n'; //输出 : truestd::cout << std::is_function<int>::value << '\n';  //输出 : falseusing T = PM_traits<decltype(&A::fun)>::member_type; // T 为 int() const&std::cout << std::is_function<T>::value << '\n';     //输出 : true
}

2.5.std::remove_extent

它的作用是给数组移除一个维度,注意若 T 是多维数组,则只移除第一维。它的实现如下:

template <class _Ty>
struct remove_extent { // remove array extentusing type = _Ty;
};template <class _Ty, size_t _Ix>
struct remove_extent<_Ty[_Ix]> {using type = _Ty;
};template <class _Ty>
struct remove_extent<_Ty[]> {using type = _Ty;
};template <class _Ty>
using remove_extent_t = typename remove_extent<_Ty>::type;

多维数组,如果要全部移除维度,则要用std::remove_all_extents, std::remove_all_extents实现如下:

template <class _Ty>
struct remove_all_extents { // remove all array extentsusing type = _Ty;
};template <class _Ty, size_t _Ix>
struct remove_all_extents<_Ty[_Ix]> {using type = typename remove_all_extents<_Ty>::type;
};template <class _Ty>
struct remove_all_extents<_Ty[]> {using type = typename remove_all_extents<_Ty>::type;
};template <class _Ty>
using remove_all_extents_t = typename remove_all_extents<_Ty>::type;

这里巧妙的借助元编程中模板类的偏特化和递归继承属性: 定义的remove_all_extents偏特化版本继承自身,通过不断地递归继承,直到remove_all_extents<T>类型与T类型完全相同时调用普通版本,完成递归调用;所有的递归调用过程均在编译期完成,从这个例子中可以看出模板元编程的真正魅力所在。

示例如下:

#include <iostream>
#include <iterator>
#include <algorithm>
#include <type_traits>template<class A>
typename std::enable_if< std::rank<A>::value == 1 >::type
print_1d(const A& a)
{copy(a, a+std::extent<A>::value,std::ostream_iterator<typename std::remove_extent<A>::type>(std::cout, " "));std::cout << '\n';
}int main()
{int a[][3] = {{1,2,3},{4,5,6}};
//  print_1d(a); // 编译时错误print_1d(a[1]);  //输出: 4  5  6
}

2.6.std::add_pointer

std::add_pointer模板可以把一个类型转换为指针类型,它的实现:

template <class _Ty, class = void>
struct _Add_pointer { // add pointer (pointer type cannot be formed)using type = _Ty;
};template <class _Ty>
struct _Add_pointer<_Ty, void_t<remove_reference_t<_Ty>*>> { // (pointer type can be formed)using type = remove_reference_t<_Ty>*;
};template <class _Ty>
struct add_pointer {using type = typename _Add_pointer<_Ty>::type;
};template <class _Ty>
using add_pointer_t = typename _Add_pointer<_Ty>::type;

示例如下:

#include <iostream>
#include <type_traits>using namespace std;int main() {typedef add_pointer<int>::type ptr; // ptr 是 int* 类型cout << is_same<int*, ptr>::value << endl;  // 输出 truereturn 0;
}

        在上面例子中,我们使用add_pointer模板将类型int转换为指针类型int*,并声明了别名ptr。可以看到,使用add_pointer模板可以快速实现类型转换。

2.7.std::conditional

std::conditional模板可以根据条件进行类型选择, 实现如下:

template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwiseusing type = _Ty1;
};template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {using type = _Ty2;
};template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;

示例如下:

#include <iostream>
#include <type_traits>
#include <typeinfo>int main() 
{typedef std::conditional<true, int, double>::type a;typedef std::conditional<false, int, double>::type b;typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type c;std::cout << typeid(a).name() << '\n';std::cout << typeid(b).name() << '\n';std::cout << typeid(c).name() << '\n';
}

3.可能实现

介绍完上面的基础知识后,根据std::decay的功能,猜测大致可能的实现如下:

template<class T>
struct decay
{
private:typedef typename std::remove_reference<T>::type U;
public:typedef typename std::conditional< std::is_array<U>::value,typename std::remove_extent<U>::type*,typename std::conditional< std::is_function<U>::value,typename std::add_pointer<U>::type,typename std::remove_cv<U>::type>::type>::type type;
};

分析步骤:

1)  擦除引用类型。例如 int&和int&& 返回 int

2)如果是数组,返回的就是数组存放的数据类型。例如:int[10] 返回 int

3)如果是函数,返回的就是函数指针。例如:int (int) 返回 int (*)(int)

4)  除2和3之外的其它类型,则擦除const、volatile 或 const volatile。

4.源码分析

根据第3部分讲解的实现流程,在VS2019上是怎么实现上面的功能呢?不隐藏,直接上源码:

template <bool>
struct _Select { // Select between aliases that extract either their first or second parametertemplate <class _Ty1, class>using _Apply = _Ty1;
};template <>
struct _Select<false> {template <class, class _Ty2>using _Apply = _Ty2;
};template <class _Ty>
struct decay { // determines decayed version of _Tyusing _Ty1 = remove_reference_t<_Ty>;using _Ty2 = typename _Select<is_function_v<_Ty1>>::template _Apply<add_pointer<_Ty1>, remove_cv<_Ty1>>;using type = typename _Select<is_array_v<_Ty1>>::template _Apply<add_pointer<remove_extent_t<_Ty1>>, _Ty2>::type;
};template <class _Ty>
using decay_t = typename decay<_Ty>::type;

从上面的代码可以看出,把std::conditional替换成了_Select,而_Select的功能和std::conditional是一模一样的,代码的逻辑和第3部分分析的也是一模一样的,在这里就不再过多赘述了。

5.使用

1)在之前我的从C++容器中获取存储数据的类型-CSDN博客中,从容器中获取所存储的数据类型,就用到了std::decay,在函数func获取跟容器中一样数据类型,如下代码:

#include <iostream>
#include <boost/type_index.hpp>template <typename Container>
void  func(Container& t, const char* pMessage) 
{using TYPE = std::decay<decltype(*t.begin())>;std::cout << pMessage << boost::typeindex::type_id_with_cvr<decltype(TYPE)>().pretty_name() << std::endl;
}

调用函数decltype(*t.begin()出来还是引用类型,通过std::decay退变成真正的原始类型Container::value_type。

2)示例如下:

#include <iostream>
#include <type_traits>template <typename T, typename U>
struct decay_equiv : std::is_same<typename std::decay<T>::type, U>::type 
{};int main()
{std::cout << std::boolalpha<< decay_equiv<int, int>::value << '\n'                    /*1*/<< decay_equiv<int&, int>::value << '\n'                   /*2*/<< decay_equiv<int&&, int>::value << '\n'                  /*3*/<< decay_equiv<const int&, int>::value << '\n'             /*4*/<< decay_equiv<int[2], int*>::value << '\n'                /*5*/<< decay_equiv<int(int), int(*)(int)>::value << '\n';      /*6*/
}

上面的代码第1部分是传入int,就是普通类型,传出也是int

上面的代码第2部分是传入int&,通过std::decay擦除左值引用,传出int

上面的代码第3部分是传入int&&,通过std::decay擦除右值引用,传出int

上面的代码第4部分是传入const int&,通过std::decay擦除左值引用,去掉const,传出int

上面的代码第5部分是传入一维数组int[2],通过std::decay移除一个维度,退化传出int*

上面的代码第6部分是传入函数int(int),通过std::decay添加指针,传出指向传入函数的指针int(*)(int)。

6.注意事项

1、虽然std::decay可以处理许多类型的转换,但是它不能处理所有的类型。例如,std::decay不能处理类类型,枚举类型,和联合类型。

2、std::decay只能用于模板参数。如果我们尝试在非模板参数上使用std::decay,编译器将会报错。

3、std::decay不能用于消除指针类型。如果我们尝试在指针类型上使用std::decay,std::decay将不会有任何效果。

7.总结

std::decay是我们平时模版编程中使用的比较多的,在实际模板编程或者模板元编程中非常有用,在type_traits源代码里处处可见,实际工作中也会经常用到;上面的介绍只是抛砖引玉,要想真正掌握它,只有的不停地使用,在使用过程中遇到问题,解决问题,才能真正理解它的原理,灵活使用。

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

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

相关文章

c++哈希表——超实用的数据结构

文章目录 1. 概念引入1.1 整数哈希1.1.1 直接取余法。1.1.2 哈希冲突1.1.2.1 开放寻址法1.1.2.2 拉链法 1.2 字符串哈希 3.结语 1. 概念引入 哈希表是一种高效的数据结构 。 H a s h Hash Hash表又称为散列表&#xff0c;一般由 H a s h Hash Hash函数(散列函数)与链表结构共同…

docker学习——汇总版

历时一个月将docker系统的学习了一下&#xff0c;并且记录了详细的笔记和实践过程。 希望能对工作需要的小伙伴们有所帮助~ docker基础篇 docker学习&#xff08;一、docker与VM对比&#xff09; docker学习&#xff08;二、安装docker&#xff09; docker学习&#xff08;…

用通俗易懂的方式讲解大模型:一个强大的 LLM 微调工具 LLaMA Factory

LLM&#xff08;大语言模型&#xff09;微调一直都是老大难问题&#xff0c;不仅因为微调需要大量的计算资源&#xff0c;而且微调的方法也很多&#xff0c;要去尝试每种方法的效果&#xff0c;需要安装大量的第三方库和依赖&#xff0c;甚至要接入一些框架&#xff0c;可能在还…

【INTEL(ALTERA)】使用 ReadFile 读取时出错: juart-terminal: error: 从 STDIO 收集输入

说明 由于 英特尔 Quartus Prime Pro Edition 软件版本 22.4 中存在一个问题&#xff0c;您在从 Windows 操作系统上的 Nios V 命令外壳输入字符时可能会看到此错误&#xff1a; 使用 ReadFile 读取时出错&#xff1a; juart-terminal&#xff1a; error&#xff1a; 从 STDI…

机器学习(二) -- 数据预处理(3)

系列文章目录 机器学习&#xff08;一&#xff09; -- 概述 机器学习&#xff08;二&#xff09; -- 数据预处理&#xff08;1-3&#xff09; 未完待续…… 目录 前言 tips&#xff1a;这里只是总结&#xff0c;不是教程哈。本章开始会用到numpy&#xff0c;pandas以及matpl…

亚信安慧AntDB数据库引领数字时代通信创新

在数字经济与实体经济深度融合的时代&#xff0c;通信行业正迎来前所未有的新机遇。特别是在中国信通院的预测中&#xff0c;2027年5G专网市场规模预计将达到802亿元&#xff0c;呈现出显著的增长态势&#xff0c;年复合增长率高达42%。 亚信安慧AntDB数据库一直致力于紧跟科技…

不同角度深入探讨Maya和Blender这两款软件的差异

当我们面对三维建模软件的选择时&#xff0c;许多初学者可能会感到迷茫。今天&#xff0c;我们将从不同角度深入探讨Maya和Blender这两款软件的差异&#xff0c;特别是对于游戏建模领域的用户来说&#xff0c;这将有助于您更好地理解两者之间的区别。 软件授权与开发背景&#…

QT上位机开发(倒计时软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 倒计时软件是生活中经常遇到的一种场景。比如运动跑步&#xff0c;比如学校考试&#xff0c;比如论文答辩等等&#xff0c;只要有时间限制规定的地…

Debezium发布历史36

原文地址&#xff1a; https://debezium.io/blog/2018/07/26/debezium-0-9-0-alpha1-released/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Debezium 0.9 Alpha1 和 0.8.1 发布 七月 26, 2018 作者&#xff…

C#,入门教程(02)—— Visual Studio 2022开发环境搭建图文教程

如果这是您阅读的本专栏的第一篇博文&#xff0c;建议先阅读如何安装Visual Studio 2022。 C#&#xff0c;入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程https://blog.csdn.net/beijinghorn/article/details/123350910 一、简单准备 开始学习、编写程序…

日志高亮 | notepad

高亮显示日志 日志文件无法清晰看到关键问题所在? 看到一堆日志头疼?高亮日志可以清晰展示出日志的 ERROR级等各种等级的问题, 一下浏览出日志关键所在 tailspin 项目地址&#xff1a; https://githubfast.com/bensadeh/tailspin 使用Rust包管理器cargo安装 安装 - Cargo 手…

3D视觉-ToF测量法(Time of Flight)

概念 ToF 是 Time of Flight 的缩写&#xff0c; ToF 测量法又被称作飞光时间测量法&#xff0c;是通过给目标连续发射激光脉冲&#xff0c;然后用传感器接收在被测平面上反射回来的光脉冲&#xff0c;通过计算光脉冲的飞行往返时间来计算得到确切的目标物距离。因为返回时间很…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存(CustomData)功能(C#)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机本身的数据保存&#xff08;CustomData&#xff09;功能&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机的数据保存&#xff08;CustomData&#xff09;功能的技术背景CameraExplorer如何使用图像剪切&#xff…

优化算法3D可视化

编程实现优化算法&#xff0c;并3D可视化 1. 函数3D可视化 分别画出 和 的3D图 import numpy as np from matplotlib import pyplot as plt import torch# 画出x**2 class Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)def for…

Python 下载与安装

1、下载 打开Python官网&#xff1a;Welcome to Python.org 点击下图所示的【Downloads】按钮进入下载页面。 ​ 进入下载页面后下拉至下图位置&#xff0c;选择版本&#xff0c;点击下载按钮下载。 页面会跳转至下一页下载页面&#xff0c;下拉到下图位置&#xff0c;选择…

PHP8使用PDO对象增删改查MySql数据库

PDO简介 PDO&#xff08;PHP Data Objects&#xff09;是一个PHP扩展&#xff0c;它提供了一个数据库访问层&#xff0c;允许开发人员使用统一的接口访问各种数据库。PDO 提供了一种用于执行查询和获取结果的简单而一致的API。 以下是PDO的一些主要特点&#xff1a; 统一接口…

荣耀之城(富饶之地)

规则简介 这是一个回合制的游戏&#xff0c;每个回合都是先选角色然后按照角色编号依次执行回合。 8个角色&#xff1a;刺客、小偷、魔术师、国王、住持、商人、建筑师、领主 根据人数的不同&#xff0c;按照不同的规则依次选取一个角色&#xff0c;国王第一个选&#xff0c…

2023年03月17日_微软和谷歌办公AI的感慨

2023年3月17日 最近这个科技圈的消息 有点爆炸的让人应接不暇了 各种大公司简直就是神仙打架 你从来没有见过这么密集的 这么高频的产品发布 昨天微软是发布了Office 365 Copilot 在里边提供了大量的AI的功能 然后谷歌呢也发布了这个Google Workspace AI 也是跟365 Cop…

『番外篇七』SwiftUI 获取视图全局位置在 NavigationStack 中失效的解决方法

概览 在 番外篇六』SwiftUI 取得任意视图全局位置的三种方法 这篇博文里,我们详细讨论了在 SwiftUI 中获取任意视图全局坐标的几种方法。 不过,我们也从中提到了某些方法无法适用于 NavigationStack 视图,本篇博文由此应运而生。 在本篇博文种,您将学到如下内容: 概览1.…

MFC连接mqtt服务器订阅和发送数据-自设计函数库

以下是一个简单的MQTT连接库文件&#xff0c;其中包含了连接、断开、订阅主题、发送数据和接收数据等函数。请注意&#xff0c;这只是一个示例&#xff0c;你可能需要根据自己的实际需求进行修改。 #include <iostream> #include <cstring> #include <sys/type…