c++之说_12|模板

关于模板,至少我们要先了解几个概念

一:函数模板

二:类模板

三:模板特化

四:形参参数包

模板覆盖的东西太多  我目前也不了解太多

函数模板 语法

template<typename 类型名,typename 类型名,typename ...多参包类型名> 
//内部的typename可写多个 有时我们可能会看到 这里会写 class 意思大概是差不多的返回值 函数名(){};
如template<typename arg, typename ...args>
void set(arg s, args... d)
{val = s;base::set(d...);
};

这里我们可以看到  template<typename arg, typename ...args>

模板形参类型 有 arg  和  args 形参包 

arg  自然就代表可接受一个参数类型,

而args 形参包也叫变参参数包 可接受多个参数

当然也有要求

一:

模板形参必须可推导,显示实例化也属于可推导

二:

有显示实例化当然也有隐式实例化

但隐式实例化有个要求

函数参数中必须可推导模板形参

template<typename arg, typename ...args>
void set(arg s, args... d)
{val = s;
};

模板形参在函数参数中直接用到了

可如此调用  这叫隐式实例化
set(10,'g',50,100); //arg = int   args = {char,int,int}这叫显示实例化
set<int,char,int,int>(10,'g',50,100); //arg = int   args = {char,int,int}

你会说 我们要是在函数参数中没一一使用到 模板形参怎么办?

template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};

自然就要显示实例化了  注意必须可推导

set<obj_2>(10, 100);
// Obj = obj_2   agr = int  args ={int}

看就显示实例化了没用到的 Obj 模板形参  我们人看上去也是可推导的 

编译器是人写的所以逻辑也是有些符合我们的思维的

函数特化有两种

偏特化(部分特化)

全特化

说实在的我也不太懂  我只能把我懂的部分 说一下

首先我们先看看 函数模板的偏特化 与 全特化

//主模版
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};//特化版本   args* 形参包中 全为 指针类型
template<typename Obj, typename arg, typename ...args>
void set(arg* s, args*... d)
{Obj c(d..., s);
}//特化版本  Ret(*s)(a...)  非成员函数的函数指针
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{Obj c((*d)..., s());
}//全特化   
template<>
void set<obj_2,float,float>(float v, float d)
{obj_2 c(d, v);
}//全特化
template<>
void set<obj_2>(float v, float d,char k)
{obj_2 c(d, v);
}

这里有个叫主模版的函数模板   用特化就必须得需要主模版  什么叫主模板?

我的理解中是  函数模板 最基本的哪个

 比如上文

template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
    Obj c(s,d...);
};

这个模板可以容纳下面几个特化出来的函数参数的样子 

比如指针类型  非成员函数的函数指针类型  成员函数的函数指针类型 

也就是说它更加全面

你说都更加全面了 我为什么还有特化

这是为了  处理不同的情况嘛   就和函数重载时  处理不同情况一样

比如


int geti()
{return 50;
}int b = 10, b2 = 100;
set<obj_2>(&b,&b2);
/*
调用的特化版本是
template<typename Obj, typename arg, typename ...args>
void set(arg* s, args*... d)
{Obj c(d..., s);
}
*/set<obj_2>(&geti, &b2);
/*
调用的特化版本是
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{Obj c((*d)..., s());
}*/set<obj_1>(0.5f, 5.3f);
/*
调用主模板版本
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};*/set<obj_2>(0.5f, 5.3f,'p');
/*
调用的特化版本是
template<>
void set<obj_2>(float v, float d,char k)
{obj_2 c(d, v);
}*/set<obj_2>(0.f);
/*
调用主模版
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};*/

有人可能看到 有些特化怎么 模板形参比主模版还多

template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{Obj c((*d)..., s());
}

这就是模板特化的一部分特性  注意 特化版本的模板形参与主模版的模板形参并无瓜葛 

就算他们是一样的名字 

但是实则是有一定要求的

比如

//主模板
template<typename Obj, typename arg>
void set1(arg c) { Obj c{}; };//全特化
template<>
void set1<obj_2,float>(float c) { obj_2 bc{}; };//错误特化版本  
template<>
void set1<obj_2,int,char>(int c) {};
/*这里我们注意到了, set1<obj_2,int,char>  有三个模板实参 而 我们的主模板只需要 两个模板实参  这就是要求:  不能大于主模版要求的模板形参数目*///错误特化版本   与上述一样   
template<>
void set1<obj_2>(int c,char b) {};

和函数重载  很类似的规则

有人会问了 那你第一种怎么可写好多个模板实参?

注意主模版哦

template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};

一眼上去三个模板形参   但是我们最后是个模板形参包啊

不限个数的啊  超过两个的形参  统统进入形参包

这里可能有少年提出这样的写法

template<typename arg, typename ...args>
void set<obj_2>(arg s, args... d)
{obj_2 c(s,d...);
};

看着 嗯.....  我就特化处理  这个obj_2类型的 

不过可惜 这样写法是错误的

注意右边的 编译输出错误

好了函数模板的特化说完了

---------------------------------------------------

现在我们来看看类模板

      

上模板

//主模版
template<typename tp>
struct t1
{
};//特化版本
template<typename Ret,typename Clss,typename ...Args>
struct t1 <Ret(Clss::*)(Args...)>
{using F = Ret(Clss::*)(Args...);t1(F fptr):ptr(fptr) {};F ptr;
};int main()
{t1<decltype(&obj_2::gets)> b(&obj_2::gets);return 0;
}

和函数模板特化差不多

类模板可通过构造函数的参数推断模板形参

template<typename tp>
struct t1
{using type = tp;t1(tp p) :tpo(p) {};tp tpo;
};template<typename Ret,typename Clss,typename ...Args>
struct t1 <Ret(Clss::*)(Args...)>
{using F = Ret(Clss::*)(Args...);t1(F fptr):ptr(fptr) {};F ptr;
};int main()
{t1<decltype(&obj_2::gets)> b(&obj_2::gets);t1 b = t1(&obj_2::gets);//特化版本 struct t1 <Ret(Clss::*)(Args...)>// F = int(obj_2::*)(int,int,char);t1(obj_2()); // 推断的是主模版  tp = obj_2return 0;
}

类模板可以弥补我们之前函数模板的遗憾

template< typename tp>
struct t2<obj_2,tp>
{t2(tp p):op(p) {}tp op;obj_2 d;};

它可以这样特化

但是又有可惜的事情了


t1 b3 = t1(obj_2()); //ok
auto c =    t2<obj_2,decltype(b3)>(b3); //ok  template< typename tp> struct t2<obj_2,tp>auto c2 = t2<obj_2>(b3); // error  可惜不可以这样调用  至少我是能看出来 应该可以推导
template<typename obj,typename tp>
struct t2
{using type = tp;t2(obj* oj,tp p) :ptr(oj), tpo(p) {};obj* ptr;tp tpo;
};//main 中t1 b = t1(&obj_2::gets);
t1 b3 = t1(obj_2());
auto c3 = t2(&b, &b3);//可以

对了  忘记说一件事了

形参包 我们通过特化给它拆开

//主模版
template<typename ...T>
struct Tuple_text {};//特化
template<>
struct Tuple_text<>
{void set() {};
};//特化
template<typename Ty1, typename ...Ty2>
struct Tuple_text<Ty1, Ty2...> :public Tuple_text<Ty2...> {Ty1 val;using base = Tuple_text<Ty2...>;Tuple_text() {}template<typename arg, typename ...args>Tuple_text(arg a, args... d) :val(a), base(d ...) {}template<typename ...arg>void set(arg... args) {};template<>void set<>() {};template<typename arg, typename ...args>void set(arg s, args... d){val = s;base::set(d...);};base& get(){return *this;}
};

这是今天学习到的  c++元组的类似做法

这里最关键的地方就是

template<>
struct Tuple_text<>
{void set() {};
};template<typename Ty1, typename ...Ty2>
struct Tuple_text<Ty1, Ty2...> :public Tuple_text<Ty2...>
{Ty1 val;
using base = Tuple_text<Ty2...>;Tuple_text() {}
template<typename arg, typename ...args>
Tuple_text(arg a, args... d) :val(a), base(d ...) {}}

没想到吧  我们的构造函数都能模板

这个有点复杂

我们展开看看   C++ Insights (cppinsights.io)  这个网站可以展开模板

#include <cstdio>//主模板
template<typename ... T>
struct Tuple_text
{
};//特化
template<>
struct Tuple_text<>
{inline void set(){}};//以下都为特化  实例化后其实也就是特化
template<>
struct Tuple_text<long> : public Tuple_text<>
{long val;using base = Tuple_text<>;inline Tuple_text();template<>inline Tuple_text<long>(long a): Tuple_text<>(), val{a}{}};template<>
struct Tuple_text<float, long> : public Tuple_text<long>
{float val;using base = Tuple_text<long>;inline Tuple_text();template<>inline Tuple_text<float, long>(float a, long __d1): Tuple_text<long>(__d1), val{a}{}};template<>
struct Tuple_text<char, float, long> : public Tuple_text<float, long>
{char val;using base = Tuple_text<float, long>;inline Tuple_text();template<>inline Tuple_text<char, float, long>(char a, float __d1, long __d2): Tuple_text<float, long>(__d1, __d2), val{a}{}};template<>
struct Tuple_text<int, char, float, long> : public Tuple_text<char, float, long>
{int val;using base = Tuple_text<char, float, long>;inline Tuple_text();template<>inline Tuple_text<int, char, float, long>(int a, char __d1, float __d2, long __d3): Tuple_text<char, float, long>(__d1, __d2, __d3), val{a}{}};int main()
{Tuple_text<int, char, float, long> c = Tuple_text<int, char, float, long>(100, 'o', 6.0F, 500L);return 0;
}

注意main 函数里

我们看到这是一系列的继承关系

我们去vs 看看内存布局

我们看到  Tuple_text<int, char, float, long> 类里面有所有的 val 

那我们应该怎么拿到呢?

Tuple_text<int, char, float, long> 的 val 很简单

但是 继承的 父类 Tuple_text<char, float, long> 的 val

怎么拿呢?

我们要是能转换为 父类对象就好了

using base = Tuple_text<Ty2...>;

base& get()
{return *this;
}

这样是不是就能拿到父类对象了?

Tuple_text<int, char, float, long>  : Tuple_text<char, float, long> :

Tuple_text<float, long> : Tuple_text<long> : Tuple_text<>

每一级的 base 都是本级继承的父类

c.val   c.get().val  c.get().get().val  c.get().get().get().val

Tuple_text<> 这个是我们自己特化的类

是一个空的 所以继承链到此终结

模板编程是面向编译器的

很强大 但是也很难以解读

模板的玩法不只这些  玩法很多很多 看大家积累了 我也需要积累

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

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

相关文章

CopyOnWriteArrayList底层原理全面解析【建议收藏】

简介 CopyOnWriteArrayList是Java中的一个线程安全的集合类&#xff0c;是ArrayList线程安全版本&#xff0c;主要通过Copy-On-Write&#xff08;写时复制&#xff0c;简称COW&#xff09;机制来保证线程安全。 Copy-On-Write机制核心思想&#xff1a;向一个数组中添加数据时…

LabVIEW动平衡测试与振动分析系统

LabVIEW动平衡测试与振动分析系统 介绍了利用LabVIEW软件和虚拟仪器技术开发一个动平衡测试与振动分析系统。该系统旨在提高旋转机械设备的测试精度和可靠性&#xff0c;通过精确测量和分析设备的振动数据&#xff0c;以识别和校正不平衡问题&#xff0c;从而保证机械设备的高…

Springboot集成jasypt实现配置文件加密

Jasypt它提供了单密钥对称加密和非对称加密两种加密方式。 单密钥对称加密&#xff1a;一个密钥加盐&#xff0c;可以同时用作内容的加密和解密依据&#xff1b; 非对称加密&#xff1a;使用公钥和私钥两个密钥&#xff0c;才可以对内容加密和解密&#xff1b; 我们以单密钥对称…

前端 reduce()用法总结

定义 reduce()方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行)&#xff0c;将其结果汇总为单个返回值。语法为&#xff1a; array.reduce(function(accumulator, currentValue, currentIndex, arr), initialValue); /*accumulator: 必需。累计器currentValu…

5 步轻松上手,教你从 0 到 1 落地 Jmeter 接口自动化脚本!

Jmeter是进行接口测试的一款非常主流的工具&#xff0c;但绝大部分测试工程师&#xff0c;对于Jmeter接口测试脚本整理都是一知半解的。今天这篇文章&#xff0c;就以一个金融项目中接口为例&#xff0c;通过简单5步&#xff0c;教大家如何0代码编写Jmeter接口自动化脚本&#…

CPU和GPU有什么区别,玩游戏哪个更重要?

大家好&#xff01;今天我们要聊的话题是CPU和GPU&#xff0c;它们在电脑中扮演着重要的角色&#xff0c;虽然看起来只是两个简单的缩写&#xff0c;但它们的功能和影响是截然不同的&#xff01; 那么&#xff0c;究竟CPU和GPU有什么区别呢&#xff1f;在玩游戏时&#xff0c;…

Linux 系统开启网络服务

首先&#xff0c;大家新装的linux系统可能都没有安装vim工具&#xff0c;所以打开文件的方式是 vi /etc/sysconfig/network-scripts/ifcfg-ens33在这个界面把onboot改为yes&#xff0c;我这里是设置完的。然后通过下面语句重新启动服务就可以了。 service network restartcen…

2024.2.7日总结(小程序开发4)

页面导航 页面导航是页面之间的相互跳转&#xff1a; <a>链接location.href 小程序中实现页面导航的两种方式&#xff1a; 声明式导航 在页面上声明一个<navigator>导航组件通过点击<navigator>组件实现页面跳转 编程式导航 调用小程序的导航API&…

飞天使-k8s知识点15-kubernetes散装知识点4-CNI网络插件与kubectl

文章目录 CNI 网络插件安装任意节点运行kubectlAPI的版本区别与废弃API查询 CNI 网络插件安装 这里将以 Calico 为例&#xff0c;提供在 Kubernetes 1.20.6 版本上安装 CNI 插件的步骤。请注意&#xff0c;具体的步骤可能会因 CNI 插件的类型和你的特定环境而略有不同。设置 Ku…

BaseMapper中提供的方法(17种CRUD)

BaseMapper封装的17种增删改查方法 MybatisPlus框架中mapper层继承了BaseMapper接口&#xff0c;该接口中封装了常用的增删改查方法&#xff0c;共有17种&#xff0c;以下是方法的详情介绍 首先需要明确的括号内的一些对象定义 泛型T&#xff1a;实体类类型Param注解&#x…

iPhone解锁 AnyMP4 iPhone Unlocker

AnyMP4 iPhone Unlocker是一款功能强大的iPhone解锁软件&#xff0c;旨在帮助用户轻松解决iPhone密码忘记、设备锁定等问题。无论是屏幕密码、指纹解锁还是Face ID&#xff0c;该软件都能提供有效的解决方案。 这款软件支持多种iPhone型号&#xff0c;包括最新的iPhone 14系列…

2.3_9 吸烟者问题

2.3_9 吸烟者问题 问题描述 问题分析 假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它&#xff0c;但是要卷起并抽掉一支烟&#xff0c;抽烟者需要有三种材料&#xff1a;烟草、纸和胶水。三个抽烟者中&#xff0c;第一个拥有烟草、第二个拥有纸…

泛娱乐社交出海洞察,Flat Ads解锁海外增长新思路

摘要:解读泛娱乐社交应用出海现状与趋势,解锁“掘金”泛娱乐社交出海赛道新思路。 根据全球舆情监测机构 Meltwater 和社交媒体机构We are Social最新发布数据显示,全球社交媒体活跃用户数量已突破50亿,约占世界人口总数62.5%。庞大的用户数量意味着广阔的增量空间,目前,随着全…

使用HCPpipelines分割皮层

前段时间阅读了一篇文献,文章的做法我比较感兴趣,所以打算学习一下文献的做法。文章的最开始一部分是使用HCPpipelines对T1和T2像进行皮层分割,调用的是freesurfer6。https://github.com/Washington-University/HCPpipelines 一、工作环境准备 1.安装好FSL,版本在6.0.2以上…

Linux环境下配置HTTP代理服务器教程

大家好&#xff0c;我是你们可爱的Linux小助手&#xff01;今天&#xff0c;我将带你们一起探索如何在Linux环境下配置一个HTTP代理服务器。请注意&#xff0c;这不是一次火箭科学的实验&#xff0c;而是一次简单而有趣的冒险。 首先&#xff0c;我们需要明确什么是HTTP代理服…

Unity AnimationRigging无法修改权重?

个人理解&#xff0c;已解决无法修改权重的问题: unity自带的动画系统是在FixUpdate和Update之后LateUpdate之前执行&#xff0c;如果在这FixedUpdate或Update函数内更新AnimationRigging内的权重后&#xff0c;内部动画系统会覆盖权重的修改&#xff0c;导致无法正确更新&…

通过docker-compose部署NGINX服务,并使该服务开机自启

要在通过docker-compose部署的NGINX服务实现开机自启&#xff0c;你需要确保Docker守护进程在系统启动时自动运行&#xff0c;并配置docker-compose.yml文件以在容器中运行NGINX服务。以下是步骤&#xff1a; 确保Docker守护进程开机启动&#xff1a; 在Ubuntu/Debian上&#x…

FL Studio21最新正式版更新下载及实用功能详解

FL Studio 21是一款功能强大的音乐制作软件&#xff0c;它经历了多个版本的更新&#xff0c;每次更新都会增加新的功能和优化现有功能&#xff0c;提高用户体验和工作效率。以下是FL Studio 21的一些版本更新、功能特点以及下载方式的介绍&#xff1a; 版本更新&#xff1a; F…

C++判断回文字符串的两种方法

解法1&#xff1a;遍历一半字符串 若字符串个数为奇数遍历到n/2 若为偶数则遍历到n/2-1中间最后只剩一个字符不需判断 &#xff08;int是向下取整的&#xff09; 假设字符串长度为len&#xff0c;字符下标从0~len-1 先看第0个和第len-1字符是否相同&#xff0c; 在看第1和第…

20240203在WIN10下使用GTX1080配置stable-diffusion-webui.git不支持float16精度出错的处理

20240203在WIN10下使用GTX1080配置stable-diffusion-webui.git不支持float16精度出错的处理 2024/2/3 21:23 缘起&#xff1a;最近学习stable-diffusion-webui.git&#xff0c;在Ubuntu20.04.6下配置SD成功。 不搞精简版本&#xff1a;Miniconda了。直接上Anacoda&#xff01; …