C++ template 模板的模板参数(5.4节)

2023-11-09

有时,让模板参数本身成为模板是很有用的,我们将继续以stack类模板作为例子,来说明模板的模板参数的用途。在Stack的例子中,如果要使用一个和缺省值不同的内部容器,程序员必须两次指定元素类型。也就是说,为了指定内部容器的类型,你需要同时传递容器的类型和它所含元素的类型。如下:
Stack<int, std::vector<int>> vStack; //使用vector的int栈
然而,借助于模板的模板参数,你可以只指定容器的类型而不需要指定所含元素的类型,就可以声明这个Stack类模板:
Stack<int, std::vector> vStack; //使用vector的int栈
为了获得这个特性,你必须把第2个模板参数指定为模板的模板参数。那么,stack的声明应该如下:
 

//stack7decl.h
#ifndef STACK7DECL_H
#define STACK7DECL_H

template <typename T,
          template <typename ELEM> class CONT = std::deque>
class Stack {
private:
    CONT<T> elems; //
public:
    void push(T const &elem);//添加元素
    void pop();//删除元素
    T top() const;//返回栈顶元素
    bool empty() const {//返回是否为空栈
        return elems.empty();
    }
};

#endif // STACK7DECL_H

不同之处在于,第2个模板参数现在被声明为一个类模板:
template <typename ELEM> class CONT
缺省值也从std::deque<T>变成std::deque。在使用时,第2个参数必须是一个类模板,并且由第一个模板参数传递进来的类型进行实例化:
CONT<T> elems;
这也是这个例子比较特别的地方:使用第1个模板参数作为第2个模板参数的实例化类型。一般地,你可以使用类模板内部的任何类型来实例化模板的模板参数。我们前面提过:作为模板参数的声明,通常可以使用typename来替换关键字class。然而,上面的CONT是为了定义一个类,因此只能使用关键字class。因此,下面的程序是正确的:
template <typename T, template <class ELEM> class CONT = std::deque>  //正确
class Stack {
    ...
};
而下面的程序却是错误的:
template <typename T, template <typename ELEM> typename CONT = std::deque> //错误
class Stack {
    ...
};
由于在这里我们并不会用到“模板的模板参数”的模板参数(即上面的ELEM),所以你可以把该名称省略不写:
template <typename T, template <typename> class CONT = std::deque> //错误
class Stack {
    ...
};

另外,还必须对成员函数的声明进行相应的修改。你必须把第2个模板参数指定为模板的模板参数;这同样适用于成同函数的实现。例如,成员函数push()的实现如下:
template <typename T, template <typename> class CONT>
void Stack<T, CONT>::push(T const &elem)
{
    elems.push_back(elem);    //把elem的拷贝附加到末端
}
还有一点需要知道:函数模板并不支持模板的模板参数。

//stack7.h
#ifndef STACK7_H
#define STACK7_H

#include <deque>
#include <stdexcept>
#include "stack7decl.h"

template <typename T, template <typename> class CONT>
void Stack<T, CONT>::push(T const &elem)
{
    elems.push_back(elem); //添加一个元素
}

template <typename T, template<typename> class CONT>
void Stack<T, CONT>::pop()
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back();
}

template <typename T, template <typename> class CONT>
T Stack<T, CONT>::top() const
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elems.back();
}

#endif // STACK7_H
//stack7test.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include "stack7.h"

int main()
{
    try {
        Stack<int>      intStack;
        Stack<float>    floatStack;

        intStack.push(42);
        intStack.push(5);

        floatStack.push(9.8f);

        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
    } catch (std::exception const &ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }

    Stack<int, std::vector> vStack;

    vStack.push(42);
    vStack.push(7);
    std::cout << "vStack.top()===" << vStack.top() << std::endl;
    vStack.pop();

    return 0;
}


模板的模板实参匹配
如果你尝试使用新版本的Stack,你会获得一个错误信息:缺省值std::deque和模板的模板参数CONT并不匹配。


对于这个结果,你或许会觉得很这诧异,但问题在于:模板的模板实参(譬如这里的std::deque)是一个具有参数A的模板,它将替换模板的模板参数(譬如这里的CONT),而模板的模板参数是一个具有参数B的模板:匹配过程要求参数A和参数B必须完全匹配;然而在这里,我们并没有考虑模板的模板实参数缺省模板参数,从而也就使B缺少了这些缺省参数值 ,当前就不能获得精确的匹配。在这个例子中,问题在于标准库中的std::deque模板还具有另一个参数:即第2个参数(也就是所谓的内存分配器allocator),它有一个缺省值,但在匹配std::deque的参数和CONT的参数时,我们并没有考虑这个缺省值。然而,解决办法总是有的,我们可以重写类的声明,让CONT的参数期待的是具有两个模板参数的容器:
template <typename T, template<typename ELEM, typename ALLOC = std::allocator<ELEM>> class CONT = std::deque>
class Stack {
private:
    CONT<T> elems;
    ...
};
同样,你可以略去ALLOC不写,因为实现中不会用不对劲它。
现在,Stack模板(包括为了能够在不同元素类型的栈之间实现相互赋值而定义的成员模板)的最终版本应该如下:

//stack8.h
#ifndef STACK8_H
#define STACK8_H

#include <deque>
#include <stdexcept>
#include <memory>

template <typename T,
          template <typename ELEM,
                    typename = std::allocator<ELEM>>
                    class CONT = std::deque>
class Stack {
private:
    CONT<T> elems;

public:
    void push(T const &elem);
    void pop();
    T top() const;
    bool empty() const {
        return elems.empty();
    }

    template<typename T2,
             template <typename ELEM2,
                       typename = std::allocator<ELEM2>>
                        class CONT2>
    Stack<T, CONT>& operator= (Stack<T2, CONT2> const &elem);
};

template <typename T, template <typename, typename> class CONT>
void Stack<T, CONT>::push(T const &elem)
{
    elems.push_back(elem);
}

template <typename T, template <typename, typename> class CONT>
void Stack<T, CONT>::pop()
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back();
}

template <typename T, template<typename, typename> class CONT>
T Stack<T, CONT>::top() const
{
    if(elems.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elems.back();
}

template <typename T, template<typename, typename> class CONT>
template <typename T2, template<typename, typename> class CONT2>
Stack<T, CONT>&
Stack<T, CONT>::operator= (Stack<T2, CONT2> const &op2)
{
    if((void*)this == (void*)&op2){
        return *this;
    }
    Stack<T2, CONT2> tmp(op2);
    elems.clear();
    while(!tmp.empty()) {
        elems.push_front(tmp.top());
        tmp.pop();
    }
    return *this;
}

#endif // STACK8_H
//stack8test.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include "stack8.h"

int main()
{
    try {
        Stack<int> intStack;
        Stack<float> floatStack;

        intStack.push(42);
        intStack.push(9);

        floatStack.push(7.9f);

        floatStack = intStack;

        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
        floatStack.pop();
    } catch (std::exception const &ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }

    Stack<int, std::vector> vStack;

    vStack.push(42);
    vStack.push(8);
    std::cout << "vStack.top()=======" << vStack.top() << std::endl;
    vStack.pop();

    return 0;
}

模板的模板参数是要求编译器符合标准的新特性之一;因此,这个程序可以作为评价你的编译器模板特性方面符合标准的尺度。
关于更深入的讨论和这方面的例子,详见8.2.3小节和15.1.6小节。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ template 模板的模板参数(5.4节) 的相关文章

  • RabbitMQ报错Error: unable to connect to node rabbit@xxx: nodedown的解决方式

    RabbitMQ报错Error unable to connect to node rabbit xxx nodedown的解决方式 环境 Win10x64 erlang otp 19 1x64 RabbitMQ3 6 6 刚开始研究Rab
  • 【前端领域】3D旋转超美相册(HTML+CSS)

    世界上总有一半人不理解另一半人的快乐 爱玛 目录 一 前言 二 本期作品介绍 3D旋转相册 三 效果展示 四 详细介绍 五 编码实现 index html style css img 六 获取源码 公众号获取源码 获取源码 私信 关注 点赞
  • KVM内核代码结构

    KVM内核代码结构 因为KVM的源代码已经包含在了Linux的内核树中 因此我们只需直接从www kernel org下载代码即可 内核源码包打开较大 解开后目录结构大概是这个样子 涉及KVM的主要有两个目录 virt和arch x86 k
  • [游戏开发]Unity业务代码自动生成工具

    前言 项目里有非常多的重复代码 例如UI业务逻辑 一般来说都会生成Manager Module View层代码 这是基本的MVC架构 Manger层 负责数据维护 对照Proto把CS和SC通信代码都写上 Module层 如果有变化则从Ma
  • 《Java基础篇》JavaBean的生命周期·作用域·通俗易懂

    1 基本概念 bean 就是由IOC 容器初始化 装配及管理的对象 Spring中的bean默认都是单例的 那么单例Bean在多线程程序下如何保证线程安全呢 Spring的单例是基于BeanFactory也就是Spring容器的 单例Bea
  • 西门子PLC入门-PLC介绍

    PLC全名 可编程逻辑控制器 Programmable Logic Controller 一种具有微处理器的用于自动化控制的数字运算控制器 可以将控制指令随时载入内存进行储存与执行 PLC由CPU 指令及数据内存 输入 输出接口 电源 数字
  • Java并发编程面试题

    基础知识 并发编程的优缺点 为什么要使用并发编程 并发编程的优点 充分利用多核CPU的计算能力 通过并发编程的形式可以将多核CPU的计算能力发挥到极致 性能得到提升 方便进行业务拆分 提升系统并发能力和性能 在特殊的业务场景下 先天的就适合
  • Hive中not in函数的小坑 :含null时的判断

    Hive中的not in函数有一个隐藏的陷阱 当not in 中的数值包含NULL 匹不上的数据会返回NULL而不是True 所以当在where中使用not in子查询进行筛选 一定要记得去除NULL值 样例代码 not in的原始结果 s
  • 打开文件,读取TXT

  • 通过HttpURLConnection连接上传文件和参数并接收

    网上使用HttpURLConnection通过get或post请求传递参数或者传递文件的例子有很多 但是同时传递参数和文件 服务的并接收参数和文件的例子很少 此文将介绍同时发送参数和文件并接收 1 HttpURLConnection简介 任
  • 产品经理的思考-我们是技术的主人吗?

    思考的起源 最近在准备公司内部的研发大会的汇报时 发现我们组的成员都跟我一样 是技术出身 在努力或者被迫努力的往技术产品经理的维度转变 晚上准备PPT到凌晨三点多 在最后收尾的时候 脑海里突然有几个疑问 在技术维 我们是技术的主人还是技术的
  • LFSR:线性反馈移位寄存器及其应用

    LFSR简介 LFSR Linear feedback shift register 是一种特殊的的移位寄存器 他的输入取决于其先前状态 LFSR的使用异常广泛 可以说涉及到方方面面 以下是Wikipedia列举的一些应用 INTELSAT
  • jquery 计算时间差

    用正则 且精确到秒 function Days1 date1 date2 var date3 date1 getTime date2 getTime 时间差的毫秒数 计算出相差天数 var days Math floor date3 24
  • Vue3使用方法

    Vue3相比于Vue2 解决了 Vue2中新增属性 删除属性 界面不会更新的问题 解决了 Vue2中直接通过下标修改数组 界面不会刷新的问题 vue cli版本必须是4 5以上 查看版本命令 vue V 创建工程命令 vue create
  • PAT(甲级)1148 Werewolf - Simple Version(枚举)

    Description 狼人杀游戏 第几个人说第几个人是狼人或者是人 规定所有人中有两个狼人 其中只有一个狼人和一个人说谎 要求找出两个狼人 Input 第一行n 代表n个人 接下来n行 正数 Di 代表 第i个人说第Di人是human 负
  • 内网服务器外网连接SSH远程端口转发实战详解

    问题 前段时间在外地没有在实验室 随身携带了一个笔记本电脑 但是笔记本性能不够 想用SSH远程连接实验室的电脑 问如何连接 现有以下设备 设备 IP 备注 系统 实验室电脑C1 192 168 0 24 内网 可访问外网 SSH登陆端口为3
  • 人脸关键点检测与 MMPose

    一 任务划分 MMPose 是利用深度学习算法对人体姿态进行估计的算法库 而人体姿态估计 是指 从给定图像中识别人脸 手部 身体等关键点 其中 输入 图像 I 输出 所有关键点的像素坐标 x 1 y 1 x 2 y 2 x j y j 这里

随机推荐

  • 【计算机网络】实验6:cisco交换机配置 在PacketTracer中对交换机设备使用命令行(CLI)进行配置

    一 实验目的 1 了解交换机的基本配置命令 2 了解交换机的VLAN配置 二 实验内容 1 packet Tracer 配置Cisco交换机 2 VLAN的配置 1 Packet Tracer 配置Cisco交换机 要配置好Cisco交换机
  • aix ---lspv command

    lspv 列出os中所有的pv pvname pvid vgname status hdisk0 00c3f005d072189b rootvg active hdisk1 00c3f435d0f4916c rootvg active hd
  • 高级软件测试成长路线-hogwarts

    高级软件测试成长路线
  • Echarts给柱状统计图、进度图的字体及柱子加不同的颜色及阴影

    今天记录一个Echart统计图中的几个小知识点以及方法 写的不好 仅供参考 如有不恰当之处 劳烦留言交流 手动抱拳 原图入选所示第一张图 改造后的图是第二张 改造前 改造后 首先看到这个图的颜色 颜色的选择按照自己的喜好和统计图所应用的场景
  • 小程序云开发入门到实践:云函数的配置与部署

    目录 1 4 云函数的配置与部署 1 4 1 云函数的开发环境 1 下载安装Node js 1 4 2 部署并上传云函数
  • Vue3封装 Message消息提示实例函数

    Vue3封装 消息提示实例函数 实现效果 Vue2 0使用 Vue prototype message function vue3 0使用app config globalProperties挂载原型方法app config globalP
  • 10 种跨域解决方案(附终极方案)

    写在前面 嗯 又来了 又说到跨域了 这是一个老生常谈的话题 以前我觉得这种基础文章没有什么好写的 会想着你去了解底层啊 不是很简单吗 但是最近在开发一个 vscode 插件 发现 当你刚入门一样东西的时候 你不会想这么多 因为你对他不熟悉
  • 苹果iOS 12.2开发者预览版beta 6发布

    3月19日消息 苹果今天推送了iOS 12 2开发者预览版Beta 6系统更新 还包括macOS 10 14 4 Mojave watchOS 5 2和tvOS 12 2的新版本 用户可以通过OTA推送升级 其中苹果iOS 12 2的Bet
  • 珍惜,那个主动联系你的人

    听过这样一句话 如果不是在乎你 谁会愿意一直主动付出 如果不是太爱你 谁又愿意低眉顺目 深以为然 感情里 一直都会主动联系你的人 才是真正在乎你 爱你的人 都说乍见之欢易 长相厮守难 再好的感情 也抵不过平淡岁月的冲刷磨砺 两个人相处久了
  • shell命令:ln -s 创建软链接采用相对路径时的奇怪用法

    目前基于测试结果得到结论 暂时无权威资料显示出原因 参考了 https blog csdn net weixin 42183399 article details 80498750 但是这个博客给的结果只是特殊用法 无法归结至一般结论 下面
  • vue中window.addEventListener(‘scroll‘, xx)失效解决办法

    多次尝试都无法获取到滚动事件 后来加上true之后就可以了 window addEventListener scroll this clintHeight true
  • js中startsWith()使用

    startsWith函数 时Java中的 在js使用时他并不是每个浏览器都有的 所以我们一般要重写一下这个函数 采用正则表达式实现startWith endWith效果函数 String prototype startWith functi
  • CPU 风扇清理灰尘加油全过程图解

    主机电源风扇由于使用时间长 风扇轴承的润滑油耗尽 导致风扇转速下降或是不转 引起电源热量无法有效排除而造成电脑经常死机 解决办法有几种 现图解说明最简单省钱的办法如下 1 把电源从主机上拆下 如下图再取出电源背面的4个固定镙丝 2008 5
  • EasyAR4.0使用说明(Unity3D)(七)----稀疏空间地图

    稀疏空间地图的对应用环境的要求和平面图像识别可以比照理解 周围环境需要足够丰富 不能有大片的单色区域 透明区域 此外 光照 角度都会对建立地图和定位产生影响 官方给出了建立地图和定位地图的建议 https help easyar cn Ea
  • rgss加密文件解包器_Galgame汉化中的逆向 (一):文本加密(压缩)与解密

    本文为看雪论坛优秀文章 看雪论坛作者ID devseed 0x0 前言 看到关于游戏汉化相关的逆向教程挺少的 作为某汉化组的成员也帮过别的汉化组 于是就想把我见到的几个典型的例子整理分析一下 还是挺有意思的 此教程和我在贴吧和隔壁发的一样
  • 时序预测

    时序预测 Python实现NARX DNN空气质量预测 目录 时序预测 Python实现NARX DNN空气质量预测 效果一览 基本介绍 研究内容 程序设计 参考资料 效果一览 基本介绍 时序预测 Python实现NARX DNN空气质量预
  • 如何使a==1&&a==2&&a==3表达式成立?

    前几天闲着无聊 玩手机无意中发现一个题 觉得挺有意思的 就顺手记录一下 题目 a 1 a 2 a 3 true 思考 我思考了一会 这让一个值既是1又是2又是3的 不可能吧 这肯定是一个伪命题 但突然我灵光一现 对象属性不是可以拦截吗 我能
  • Flex 构建路径

    然libs文件夹是构建路径的一部分 但它并不总是SWC的理想存放位置 当多个项目同时使用相同的SWC时 就不能都存放在libs文件夹中 在这种情况下 SWC可以保持在中心位置 众所周知 SWC路径可以被添加到构建路径中 虽然这意味着需要建立
  • 问题 G: 用递归的方法求值

    题目描述 求1 2 3 4 5 n的值 输入格式 一个n n不大于10000 输出格式 输出1到n的累加和 输入样例 复制 2 输出样例 复制 3 这道题比较简单 边界是n 0 核心代码为 if n 0 return 0 else retu
  • C++ template 模板的模板参数(5.4节)

    有时 让模板参数本身成为模板是很有用的 我们将继续以stack类模板作为例子 来说明模板的模板参数的用途 在Stack的例子中 如果要使用一个和缺省值不同的内部容器 程序员必须两次指定元素类型 也就是说 为了指定内部容器的类型 你需要同时传