C++ 之 模板与泛型编程(二、模板实例化)

2023-11-07

 模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程称为实例化。

模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。

 

类的实例化

模板的定义:


     template <class Type> class Queue {
     public:
         Queue ();                // default constructor
         Type &front ();          // return element from head of Queue
         const Type &front () const;
         void push (const Type &); // add element to back of Queue
         void pop();              // remove element from head of Queue
         bool empty() const;      // true if no elements in the Queue
     private:
         // ...
     };

当定义模板类对象时,

 

     Queue<int> qi;

 

编译器自动创建名为 Queue 的类。实际上,编译器通过重新编写 Queue 模板,用类型 int 代替模板形参的每次出现而创建 Queue 类。实例化的类就像已经编写的一样:

// simulated version of Queue instantiated for type int
     class Queue<int> {
     public:
         Queue();                  // this bound to Queue<int>*
         int &front();             // return type bound to int
         const int &front() const; // return type bound to int
         void push(const int &);   // parameter type bound to int
         void pop();               // type invariant code
         bool empty() const;       // type invariant code
     private:
         // ...
     };

 

Queue<int> qi;
Queue          qi; //error

 

用模板类定义的类型总是模板实参。例如,Queue 不是类型,而 Queue<int>Queue<string> 是类型

 

函数模板实例化

使用函数模板时,编译器通常会为我们推断模板实参

int main()
     {
        compare(1, 0);             // ok: binds template parameter to int
        compare(3.14, 2.7);        // ok: binds template parameter to double
        return 0;
     }

这个程序实例化了 compare 的两个版本:一个用 int 代替 T,另一个用 double 代替 T,实质上是编译器为我们编写了 compare 的这两个实例:

     int compare(const int &v1, const int &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }
     int compare(const double &v1, const double &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }
模板实参推断

第一个调用 compare(1, 0) 中,实参为 int 类型;第二个调用 compare(3.14, 2.7) 中,实参为 double 类型。从函数实参确定模板实参的类型和值的过程叫做模板实参推断

 

1、多个类型形参的实参必须完全匹配

     template <typename T>
     int compare(const T& v1, const T& v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }
     int main()
     {
         short si;
         // error: cannot instantiate compare(short, int)
         // must be: compare(short, short) or
         // compare(int, int)
         compare(si, 1024);
         return 0;
     }

 

这个调用是错误的,因为调用 compare 时的实参类型不相同,从第一个实参推断出的模板类型是 short,从第二个实参推断出 int 类型,两个类型不匹配。

 

如果 compare 的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义:

     // argument types can differ, but must be compatible
     template <typename A, typename B>
     int compare(const A& v1, const B& v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

   现在用户可以提供不同类型的实参了:

     short si;
     compare(si, 1024); // ok: instantiates compare(short, int)

2、类型形参的实参的受限转换

    考虑下面的 compare 调用:

     short s1, s2;
     int i1, i2;
     compare(i1, i2);           // ok: instantiate compare(int, int)
     compare(s1, s2);           // ok: instantiate compare(short, short)

如果 compare(int, int) 是普通的非模板函数,则第二个调用会匹配那个函数,short 实参将提升(第 5.12.2 节)为 int。因为 compare 是一个模板,所以将实例化一个新函数,将类型形参绑定到 short。

一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:

const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。

数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。

 

 template <typename T> T fobj(T, T); // arguments are copied
     template <typename T>
     T fref(const T&, const T&);       // reference arguments
     string s1("a value");
     const string s2("another value");
     fobj(s1, s2);     // ok: calls f(string, string), const is ignored
     fref(s1, s2);     // ok: non const object s1 converted to const reference
     int a[10], b[42];
     fobj(a, b); // ok: calls f(int*, int*)
     fref(a, b); // error: array types don't match; arguments aren't converted to pointers

第一种情况下,传递 string 对象和 const string 对象作为实参,即使这些类型不完全匹配,两个调用也都是合法的。在 fobj 的调用中,实参被复制,因此原来的对象是否为 const 无关紧要。在 fref 的调用中,形参类型是 const 引用,对引用形参而言,转换为 const 是可以接受的转换,所以这个调用也正确。

 

在第二种情况中,将传递不同长度的数组实参。fobj 的调用中,数组不同无关紧要,两个数组都转换为指针,fobj 的模板形参类型是 int*。但是,fref 的调用是非法的,当形参为引用时(第 7.2.4 节),数组不能转换为指针,ab 的类型不匹配,所以调用将出错。

 

3、应用于非模板实参的常规转换

用普通类型定义的形参可以使用常规转换

template <class Type> Type sum(const Type &op1, int op2)
     {
         return op1 + op2;
     }

因为 op2 的类型是固定的,在调用 sum 的时候,可以对传递给 op2 的实参应用常规转换:

     double d = 3.14;
     string s1("hiya"), s2(" world");
     sum(1024, d); // ok: instantiates sum(int, int), converts d to int
     sum(1.4, d); // ok: instantiates sum(double, int), converts d to int
     sum(s1, s2); // error: s2 cannot be converted to int

 

4、模板实参推断与函数指针

可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。

 

例如,假定有一个函数指针指向返回 int 值的函数,该函数接受两个形参,都是 const int 引用,可以用该指针指向 compare 的实例化

     template <typename T> int compare(const T&, const T&);
     // pf1 points to the instantiation int compare (const int&, const int&)
     int (*pf1) (const int&, const int&) = compare;

pf1 的类型是一个指针,指向“接受两个 const int& 类型形参并返回 int 值的函数”,形参的类型决定了 T 的模板实参的类型,

T 的模板实参为 int 型,指针 pf1 引用的是将 T 绑定到 int 的实例化。

 

如果不能从函数指针类型确定模板实参,就会出错。例如,假定有两个名为 func 的函数,每个函数接受一个指向函数实参的指针。func 的第一个版本接受有两个 const string 引用形参并返回 string 对象的函数的指针,func 的第二个版本接受带两个 const int 引用形参并返回 int 值的函数的指针,不能使用 compare 作为传给 func 的实参:

     // overloaded versions of func; each take a different function pointer type
     void func(int(*) (const string&, const string&));
     void func(int(*) (const int&, const int&));
     func(compare); // error: which instantiation of compare?

 

函数模板的显式实参

在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。在这种情况下,有必要覆盖模板实参推断机制,并显式指定为模板形参所用的类型或值

 

指定显式模板实参

考虑下面的问题。我们希望定义名为 sum、接受两个不同类型实参的函数模板,希望返回类型足够大,可以包含按任意次序传递的任意两个类型的两个值的和,怎样才能做到?应如何指定 sum 的返回类型?

 

     // T or U as the returntype?
     template <class T, class U> ??? sum(T, U);

在这个例子中,答案是没有一个形参在任何时候都可行,使用任一形参都一定会在某些时候失败:

解决这一问题的一个办法,可能是强制 sum 的调用者将较小的类型强制转换为希望作为结果使用的类型:

 

    // ok: now either T or U works as return type
     int i; short s;
     sum(static_cast<int>(s), i); // ok: instantiates int sum(int, int)

在返回类型中使用类型形参

指定返回类型的一种方式是引入第三个模板形参,它必须由调用者显式指定:

     // T1 cannot be deduced: it doesn't appear in the function parameter list
     template <class T1, class T2, class T3>
     T1 sum(T2, T3);

 这个版本增加了一个模板形参以指定返回类型。只有一个问题:没有实参的类型可用于推断 T1 的类型,相反,调用者必须在每次调用 sum 时为该形参显式提供实参。

为调用提供显式模板实参与定义类模板的实例很类似,在以逗号分隔、用尖括号括住的列表中指定显式模板实参。显式模板类型的列表出现在函数名之后、实参表之前:

    // ok T1 explicitly specified; T2 and T3 inferred from argument types
     long val3 = sum<long>(i, lng); // ok: calls long sum(int, long)

 

显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推。假如可以从函数形参推断,则结尾(最右边)形参的显式模板实参可以省略。如果这样编写 sum 函数:

 

// poor design: Users must explicitly specify all three template parameters
     template <class T1, class T2, class T3>
     T3 alternative_sum(T2, T1);

则总是必须为所有三个形参指定实参:

 

 // error: can't infer initial template parameters
     long val3 = alternative_sum<long>(i, lng);
     // ok: All three parameters explicitly specified
     long val2 = alternative_sum<long, int, long>(i, lng);

显式实参与函数模板的指针

可以使用显式模板实参的另一个例子是有二义性程序,通过使用显式模板实参能够消除二义性

 

template <typename T> int compare(const T&, const T&);
     // overloaded versions of func; each take a different function pointer type
     void func(int(*) (const string&, const string&));
     void func(int(*) (const int&, const int&));

 

像前面一样,需要在调用中传递 compare 实例给名为 func 的重载函数。只查看不同版本 func 的形参表来选择传递 compare 的哪个实例是不可能的,两个不同的实例都可能满足该调用。显式模板形参需要指出应使用哪个 compare 实例以及调用哪个 func 函数。


     func(compare<int>); // ok: explicitly specify which version of compare

 

 

 

 

 

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

C++ 之 模板与泛型编程(二、模板实例化) 的相关文章

随机推荐

  • 【Python】编程练习:十进制整数转二进制

    文章目录 题目描述 输入格式 输出格式 测试样例 样例输入 样例输出 参考代码 题目描述 十进制整数转二进制的方法是 除以2 取出余数 商继续除以2 直到得到0为止 将取出的余数逆序即可得到对应的二进制数的各位 例如 22 转二进制的计算过
  • c++递归函数

    递归函数是将规模较大的问题化为多个规模较小的同类问题 递归函数的特征是定义中包含函数本身和必须有终止条件 递归调用分为两个阶段 递推和回归 递推 将大问题转化为多个小问题 将问题逐步由未知化为已知 回归 从已知出发 是递推的逆过程 逐个求值
  • (面经三,技术面)——时间:2022-11-11 地点:线上

    面试经历 三 时间 2022 11 11 地点 线上 1 什么是抽象类 有抽象方法的类 用来表征对问题领域进行分析 设计中得出的抽象概念 2 抽象类和接口的区别 继承关系 类只能单继承 接口可以实现多个接口 构造函数 抽象类可以有构造函数
  • 王者荣耀学习工具JAVA Robot按键精灵找色点击功能

    一个简单的JAVA按键精灵 import java awt AWTException import java awt Color import java awt MouseInfo import java awt Point import
  • Pandas(一)—— Pandas基础

    Python模块 Pandas Pandas 一 Pandas基础 一 预备知识 1 1 Python基础 1 2 NumPy基础 1 3 练习 1 3 1 利用列表推导式写矩阵乘法 1 3 2 更新矩阵 1 3 3 连续整数的最大长度 二
  • 编程分钟转化小时怎么编_大中小型三菱PLC分类及编程软件学习内容附图文,初学者必看!...

    不少初学PLC的人都会问一个问题 如果学习三菱PLC编程入门应先学习什么 以下是关于PLC类型和运行原理 GX Developer软件的使用说明 如果这篇文章对你有所帮助 记得 收藏 一 PLC的类型 1 小型PLC 一体式结构 I O点数
  • 从Docker到Kubernetes——K8s多租户管理与资源控制

    文章目录 namespace设计解读 什么是namespace 多namespace使用案例 Kubernetes用户认证机制 应用健康检查 namespace设计解读 namespace是k8s进行多租户资源隔离主要手段 那么它在系统中的
  • 关于如何解决mingw64安装后配置完环境变量仍然执行不了gcc命令

    一 背景 已安装对应版本的mingw64 并且已经按照网上教程配置了环境变量 但是仍然无法执行gcc命令 二 遇到的问题 gcc 不是内部或外部命令 也不是可运行的程序 三 如何解决 至于如何打开环境变量 我就不赘述了 想必大家配置环境变量
  • GO语言变量声明的方式

    1 指定变量类型 声明后如果不做赋值 那么使用默认值 1 2 没有进行赋值 但是指明了是什么类型 go会自动加上空值 var a string 2 不指定变量类型 需要进行赋值操作 1 2 可以不指定变量类型 通过变量值 go会自行判断该变
  • Element-ui使用@keyup.enter.native的原因

    keyup enter加 native有什么作用 W3C 标准中有如下规定 即 当一个 form 元素中只有一个输入框时 在该输入框中按下回车应提交该表单 如果希望阻止这一默认行为 可以在 标签上添加 submit native preve
  • linux proc进程,linux 下 /proc/进程号/ 重要进程文件的内容解析

    proc maps 查看进程的虚拟地址空间是如何使用的 该文件有6列 分别为 地址 库在进程里地址范围 权限 虚拟内存的权限 r 读 w 写 x s 共享 p 私有 偏移量 库在进程里地址范围 设备 映像文件的主设备号和次设备号 节点 映像
  • 少儿编程scratch与机器人

    少儿编程scratch与机器人 对于很多的家长们来说 孩子的学习一直都是家长们十分关心和重视的一件事情 很多的家长在培养孩子的学习的时候 会给孩子选择一些能够提升孩子能力的课程 就拿现在很多的家长想要孩子去学习机器人编程的课程来说 他们对于
  • css的标准写法,前端css书写规范

    代码书写规范这种东西虽然不是必须的但是确实很有必要的好的书写规范不单单能增加可阅读性和提高代码性能而且有利于后续人员维护代码 良好的书写规范也是区分新手与专业人员的一个标准 一css书写顺序 1 位置属性position top right
  • VUE项目引入微信jssdk

    我们的一个vue webpack的 SPA项目需要在微信中使用 因此需要导入微信的jssdk库 通过搜索发现npm有微信jssdk的依赖包 weixin js sdk 于是 导入依赖包 npm i S weixin js sdk 前端页面U
  • Log 利用装饰模式 打印增强

    包引用
  • 前端基础--JavaScript

    一 JavaScript介绍 虽然是java作为前缀 但java和javascript的关系 就像老婆和老婆饼之间的关系 没有一毛钱关系 网景公司在Netscape2 0首先推出了JavaScript JavaScript 的正式名称是 E
  • Windows应急响应

    临近冬奥 残奥 发一篇Windows的应急响应 希望对大家有所帮助 下一篇会发Linux的应急响应 目录 Part1 前期交互 Part2 主机排查 Part3 工具篇 Part1 前期交互 这个阶段主要是先找客户了解主机的基本情况 如 主
  • Push failed Remote: Support for password authentication was removed on August 13, 2021.

    啥情况 竟然不能推送代码到github了 还能不能愉快的玩耍了 报错如下 大概意思就是 8月13后 不能使用用户名 密码的形式进行Push 强制大家改成SSH的方式 先说解决方案吧 一 查看是否已有密钥 Mac Users Your Nam
  • 写好“提示”改变“智造未来”-GPT4提示词驶入代码优化驾驶座心得

    开篇 在前端科技的新浪潮中 Artificial Intelligence AI 的逐渐成熟与发展引领着我们向前 其中OpenAI的GPT4提供了我们一种新的可能 帮助我们优化代码 使编程变得更加轻松 在这篇文章中 我们将一同探究如何在1
  • C++ 之 模板与泛型编程(二、模板实例化)

    模板是一个蓝图 它本身不是类或函数 编译器用模板产生指定的类或函数的特定类型版本 产生模板的特定类型实例的过程称为实例化 模板在使用时将进行实例化 类模板在引用实际模板类类型时实例化 函数模板在调用它或用它对函数指针进行初始化或赋值时实例化