目录

  • 定义函数模板
  • 使用函数模板
  • 两阶段翻译 Two-Phase Translation
    • 模板的编译和链接问题
  • 多模板参数
    • 引入额外模板参数作为返回值类型
    • 让编译器自己找出返回值类型
    • 将返回值声明为两个模板参数的公共类型
  • 默认模板参数
  • 重载函数模板
    • 重载时最好不要随便改变模板参数个数,最好可以显示的指定模板参数类型
    • 确保所有被重载的函数模板在使用时已经被声明定义

定义函数模板

template<typename T>T max(T a,T b) {  return b }

使用函数模板

  std::cout <7,42) <std::endl;

  std::cout <1.1,2.2) <std::endl;

  std::cout <"math","mathematics") <std::endl;

模板不是被编译成可以处理任何类型的单个函数。相反,编译器会针对每一个使用该模板的类型生成对应的函数。例如,max(7,42)的调用在语义上相当于调用了:

int max(int a,int b) {  return b }

double、string 同理。

将模板参数替换成具体参数类型的过程叫做instantiation,这个过程会产生一个instance of template

两阶段翻译 Two-Phase Translation

如果某一特定参数类型不支持模板内的操作,那么编译阶段会报错,例如:

std::complex<float> c1,c2;        //不支持 max中的 ...max(c1,c2);

模板会分成两个阶段进行”编译“:

  1. 在不进行模板instantiationdefinition time阶段,此时会忽略模板参数,检查如下方面:
  • 语法错误,包括缺失分号。
  • 使用未定义参数。
  • 如果 static assertion 不依赖模板参数,会检查是否通过 static assertion.

instantiation阶段,会再次检查模板里所有代码的正确性,尤其是那些依赖模板参数的部分。

例如:

template<typename T>void foo(T t) {  undeclared();         // first-phase compile-time error if undeclared() unknown

  undeclared(t);       // second-phase compile-time error if undeclared(T) unknown

  static_assert(sizeof(int) > 10,"int too small");      // first-phase compile-time error

  static_assert(sizeof(T) > 10, "T too small");        // second-phase compile-time error

}

模板的编译和链接问题

大多数人会按照如下方式组织非模板代码:

  • 将类或者其他类型声明放在头文件(.hpp、.H、.h、.hh、.hxx)中。
  • 将函数定义等放到一个单独的编译单元文件中(.cpp、.C、.c、.cc、.cxx)。

但是这种组织方式在包含模板的代码中却行不通,例如:头文件:

// myfirst.hpp#ifndef MYFIRST_HPP#define MYFIRST_HPP// declaration of templatetemplate<typename T>void printTypeof (T const&);#endif // MYFIRST_HPP

定义函数模板的文件:

// myfirst.cpp#include #include #include "myfirst.hpp"// implementation/definition of templatetemplate<typename T>void printTypeof (T const& x) {  std::cout <typeid(x).name() <'\n';}

在另一个文件中使用该模板:

// myfirstmain.cpp#include "myfirst.hpp"// use of the templateint main() {  double ice = 3.0;  printTypeof(ice); // call function template for type double}

在 c/c++中,当编译阶段发现一个符号(printTypeof)没有定义只有声明时,编译器会假设它的定义在其他文件中,所以编译器会留一个”坑“给链接器 linker,让它去填充真正的符号地址。

但是上面说过,模板是比较特殊的,需要在编译阶段进行instantiation,即需要进行模板参数类型推断,实例化模板,当然也就需要知道函数的定义。但是由于上面两个 cpp 文件都是单独的编译单元文件,所以当编译器编译myfirstmain.cpp时,它没有找到模板的定义,自然也就没有instantiation

解决办法就是我们把模板的声明和定义都放在一个头文件。大家可以看一下自己环境下的 vector 等 STL 源文件,就是把类的声明和定义都放在了一个文件中。

多模板参数

template<typename T1, typename T2>T1 max (T1 a, T2 b) {    return b }...auto m = max(4, 7.2);       // 注意:返回类型是第一个模板参数T1 的类型

但是问题正如注释中说的,max 的返回值类型总是 T1。如果我们调用max(42, 66.66),返回值则是 66。

一般有三个方法解决这个问题:

  • 引入额外模板参数作为返回值类型
  • 让编译器自己找出返回值类型
  • 将返回值声明为两个模板参数的公共类型,比如 int 和 float,公共类型就是 float

引入额外模板参数作为返回值类型

在函数模板的参数类型推导过程中,一般我们不用显式指定模板参数类型。但是当模板参数不能根据传递的参数推导出来时,我们就需要显式的指定模板参数类型。

template<typename T1, typename T2, typename RT>RT max(T1 a, T2 b);

RT 是不能根据函数的参数列表推导出来的,所以我们需要显式的指定:

max<int, double, double>(4, 7.2);

或者我们改变模板参数列表顺序,这种情况只需显式的指定一个参数类型即可:

template<typename RT typename T1, typename T2>      //RT变为第一个模板参数RT max(T1 a, T2 b);...max<double>(4, 7.2);

让编译器自己找出返回值类型

在 C++11 中,我们可以利用 auto 和 trailing return type 来让编译器找出返回值类型:

template <typename T1, typename T2>auto max(T1 a, T2 b) -> decltype(b  {return b }

decltype 后面的文章会讲到,这里只需知道它可以获取到表达式的类型。

我们可以写的更简单点:

template <typename T1, typename T2>auto max(T1 a, T2 b) -> decltype(true ? a : b) {      // true ? a : b  return b }

关于?:返回值规则可以参考这个:[Conditional Operator: ? :]()

看到true ? a : b不要奇怪为什么是 true,这里的重点不是计算返回值,而是得到返回值类型。

在 C++14 中,我们可以省略 trailing return type:

template<typename T1, typename T2>auto max (T1 a, T2 b) {    return b }

将返回值声明为两个模板参数的公共类型

c++11 新特性std::common_type可以产生几个不同类型的共同类型,其实核心意思跟上面说的差不多:

template <typename T1, typename T2>typename std::common_type::type max(T1 a, T2 b) {return b }

在 c++14 中,可以更简单的写:

template <typename T1, typename T2>std::common_type_t max(T1 a, T2 b) {  return b }

这里使用_t后缀让我们不用写typename::type。类似的还有_v,这个在 c++14 的type traits里很常见。

默认模板参数

这个很像函数的默认参数,直接看例子:

template <typename T1, typename T2, typename RT = std::common_type_t>RT max(T1 a, T2 b) {return b }auto a = max(4, 7.2);auto b = max<double,int,long double>(7.2, 4);

正如第二个用法,如果我们想显示的指明 RT 的类型,必须显示的指出全部三个参数类型。但是与函数默认参数不同的是,我们可以将默认参数放到第一个位置:

template <typename RT = long, typename T1, typename T2>RT max(T1 a, T2 b) {  return b }

int i;long l;…max(i, l);                     // 返回值类型是long (RT 的默认值)max<int>(4, 42);      //返回int,因为其被显式指定

重载函数模板

这个跟普通函数重载也类似:

// maximum of two int values:int max(int a, int b) {  return b }

// maximum of two values of any type:template <typename T>T max(T a, T b) {  return b }

int main() {  max(7, 42);         // calls the nontemplate for two ints  max(7.0, 42.0);     // calls max (by argument deduction)  max('a', 'b');      // calls max (by argument deduction)  max<>(7, 42);       // calls max (by argument deduction)  max<double>(7, 42); // calls max (no argument deduction)  max('a', 42.7);     // calls the nontemplate for two ints}

这里需要解释下最后一个max('a', 42.7)因为在模板参数类型推导过程中不允许类型自动转换,但是调用普通函数是允许的,所以这个会调用非模板函数。

ps. 由于函数模板重载,所以函数模板并不像类模板一样可以进行偏特化。

还有两点关于重载的基本原则需要了解一下:

重载时最好不要随便改变模板参数个数,最好可以显示的指定模板参数类型

下面是段有问题的代码:

// maximum of two values of any type (call-by-reference)template <typename T> T const &max(T const &a, T const &b) {  return b }

// maximum of two C-strings (call-by-value)char const *max(char const *a, char const *b) {  return std::strcmp(b, a) 0 ? a : b;}

// maximum of three values of any type (call-by-reference)template <typename T> T const &max(T const &a, T const &b, T const &c) {  return max(max(a, b), c);           // error if max(a,b) uses call-by-value}

int main() {  auto m1 = max(7, 42, 68);         // OK

  char const *s1 = "frederic";  char const *s2 = "anica";  char const *s3 = "lucas";  auto m2 = max(s1, s2, s3);         // run-time ERROR}

问题出现在return max (max(a,b), c);,因为char const *max(char const *a, char const *b)的参数是按值传递,max(a,b)会产生一个指向已经销毁的栈帧地址,这会导致未定义行为。

确保所有被重载的函数模板在使用时已经被声明定义

// maximum of two values of any type:template <typename T>T max(T a, T b) {  std::cout <"max()\n";return b }// maximum of three values of any type:template <typename T>T max(T a, T b, T c) {return max(max(a, b), c);}// maximum of two int values:int max(int a, int b) {std::cout <"max(int,int) \n";return b }int main() {  max(47, 11, 33);    // max()}

这点很好理解。

(完)

binarytreenode”使用 类 模板 需要 模板 参数列表_c++1117 模板核心知识(一)—— 函数模板...相关推荐

  1. C++ TGP 模板基础知识--01函数模板

    基本范例 模板的定义是以template关键字开头 类型模板参数T前面用typename来修饰,所以遇到typename就知道其后面跟的是一个类型,typename可以用class取代 类型模板参数T ...

  2. C++(13)--函数的进阶:内联、传递引用、参数默认值、重载、函数模板

    模块化编程--函数的进阶 1.内联函数 1.1 inline基本情况 1.2 inline 的前世今生-带参的宏替换 2.传递引用(重点) 2.1引用.理由.注意事项 2.3 交换两个变量的数值 3. ...

  3. C++基础:模板,函数模板和类模板

    文章目录 1. 函数模板 2. 类模板 3. 模板特化 3.1 函数模板特化 3.2 类模板特化 4. 非类型模板参数 模板是允许函数或类通过泛性的形式表现或运行的特性 1. 函数模板 模板可以使函数 ...

  4. 函数模板和类模板详解

    C++ 中的模板主要是函数模板和类模板 大致可以分为:模板函数 和模板类 其中模板类主要解决对象问题  . 第一部分:函数模板 1.在 C++ 中为了操作简洁我们引入了函数模板.所谓的函数模板实际上是 ...

  5. 笔记②:牛客校招冲刺集训营---C++工程师(面向对象(友元、运算符重载、继承、多态) -- 内存管理 -- 名称空间、模板(类模板/函数模板) -- STL)

    0618 C++工程师 第5章 高频考点与真题精讲 5.1 指针 & 5.2 函数 5.3 面向对象(和5.4.5.5共三次直播课) 5.3.1 - 5.3.11 5.3.12-14 友元 友 ...

  6. C++笔记7:C++提高编程1:模板—[函数模板和类模板]

    0820 C++提高编程: 1.模板-[函数模板和类模板] 2.初识STL 3.STL-常用容器 4.STL-函数对象 5.STL-常用算法 C++提高编程引言: C++除了面向对象编程思想,还有泛型 ...

  7. 模板 (函数模板语法 ,类模板与函数模板的区别,:函数模板案例,普通函数与函数模板的区别,普通函数与函数模板调用规则,模板的局限性,类模板分文件编写.cpp,Person.hpp,类模板与友元)

    **01:函数模板语法: #include<iostream> using namespace std;//交换两个整型函数 void swapInt(int &a ,int &a ...

  8. C++模板函数 模板类

    思考:为什么要引入模板呢? 对于这个问题,我们通过一个例子让大家切实体会一下,模板给我们带来的好处. 当我们要写一个比较大小的函数时,如果我们要比较的两个数是整数,那么,我们往往会这样来定义: 首先, ...

  9. 6、 函数模板和类模板

    函数模板和类模板 前言 C++提供了函数模板(functiontemplate).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数 ...

最新文章

  1. as安装过程中gradle_柯赛分享:楼顶大字安装过程中需要用到哪些工具?
  2. 程序员的成长过程 | 每日趣闻
  3. 横线-文字-横线的html-css布局
  4. capdriverconnect 黑色_【中信期货黑色(动力煤)】港口库存低位,结构性矛盾突出——周报20201101...
  5. 【干货】CRM大牛告诉你,Salesforce到底是个什么鬼?
  6. tcp 组播_华为组播理论知识详解(二)
  7. HTTP--历史、组件系统
  8. Upload LABS Pass-11
  9. .NET Core的文件系统[2]:FileProvider是个什么东西?
  10. 非平衡电桥电阻计算_直流双臂电桥使用方法,统统告诉你
  11. excel中如何对比两个表格的重复数据
  12. knockout的监控数组实现 - 司徒正美
  13. linux微信登陆失败,微信登不上去怎么办 微信登陆失败解决方法
  14. 【Love2d从青铜到王者】第十五篇:Love2d之角度和距离(Angles and distance)
  15. matlab打靶法求解薛定谔方程,用MATLAB语言解氢原子与类氢离子的定态薛定谔方程...
  16. java订单超时取消设计_订单超时30分钟自动取消
  17. 网格化运维标书_运维网格化管理.ppt
  18. nRF52840-DK通过USB接电脑,不能识别出Jlink
  19. linux扩充文件夹容量,Linux扩充磁盘空间 【附查看磁盘命令】
  20. 什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?

热门文章

  1. 什么是 CAS 机制?
  2. SpringBoot启动过程详解
  3. UNIX:缓冲区和重定向
  4. 后台开发技术(1)--概述
  5. 计算机网络 公有IP和私有IP介绍
  6. dma访问主存时_STM32F103单片机(五)——DMA
  7. solidworks钣金插件_SolidWorks钣金设计实例:等径弯管
  8. sql发送邮件 html,T-SQL;如何在表之前和之后以HTML格式使用sp_send_dbmail
  9. c语言fgetpos的参数,C语言fgetpos()函数:获得当前文件的读写指针(转)
  10. 如何成为一名数据中心运营工程师?