首页 > 编程语言 >C++模板——函数模板

C++模板——函数模板

时间:2022-11-16 12:12:10浏览次数:62  
标签:return 函数 int max C++ 参数 template 模板

1.1 定义函数模板

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

1.2 使用函数模板

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

  std::cout << max(1.1,2.2) << std::endl;
  
  std::cout << max("math","mathematics") << std::endl;

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

int max(int a,int b) {
  return b < a ? a : b;
}

double、string同理。

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

image

1.3 两阶段翻译 Two-Phase Translation

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

std::complex<float> c1,c2;        //不支持 max中的 < 操作,编译阶段会报错
...
max(c1,c2);

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

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

}

image

1.3.1 模板的编译和链接问题

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

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

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

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

定义函数模板的文件:

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

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

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

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

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

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

1.4 多模板参数

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

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

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

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

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

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

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);

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

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

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

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

我们可以写的更简单点:

template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(true ? a : b) {      // true ? a : b
  return b < a ? a : 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 < a ? a : b;
}

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

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

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

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

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

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

image

1.5 默认模板参数

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

template <typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>
RT max(T1 a, T2 b) {
  return b < a ? a : 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 < a ? a : b;
}

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

1.6 重载函数模板

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

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

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

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

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

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

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

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

下面是段有问题的代码:

// maximum of two values of any type (call-by-reference)
template <typename T> T const &max(T const &a, T const &b) {
  return b < a ? a : 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)会产生一个指向已经销毁的栈帧地址,这会导致未定义行为。

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

// maximum of two values of any type:
template <typename T> 
T max(T a, T b) {
  std::cout << "max<T>()\n";
  return b < a ? a : 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 < a ? a : b;
}

int main() {
  max(47, 11, 33);    // max<T>()
}

这点很好理解。

(完)

朋友们可以关注下我的公众号,获得最及时的更新:

标签:return,函数,int,max,C++,参数,template,模板
From: https://www.cnblogs.com/arthurzyc/p/16895448.html

相关文章

  • js定义函数的三种方法及区别
    1函数定义:2functiontest(){};3函数表达式|匿名函数4vartest=function(){};5构造函数6vartest=newFunction('a','b','函数体');functio......
  • 第2章 C++编程入门、输入/输出和运算符(笔记)
    2.1简介说明接下来的程序是对数据的获取、处理(算术运算)、显示。2.2第一个C++程序:输出一行文本用一个简单的程序说明C++语言的几个重要特征。注释、预处理指令、空行......
  • CSP 201403-1 相反数 C++
    1#include<iostream>2#include<vector>3#include<algorithm>45intmain(){6intx{},sum{};7std::cin>>x;8std::vector<int>n(......
  • C++初阶(类的访问权限以及封装+this指针+构造函数+析构函数+拷贝构造函数+参数列表+友
    面向过程与面向对象C语言是面向过程的,关注的是过程(函数),分析出求解问题的步骤,通过函数调用逐步解决问题。C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠......
  • C++11新特性系列
    C++11/14特性系列1.variadictemplates(可变参数模板)//需要处理最后一个无参的情况voidprint(){}template<typename...Types>voidprint(constTypes&.........
  • 算法基础:离散化及模板详解
    ⭐写在前面的话:本系列文章旨在复习算法刷题中常用的基础算法与数据结构,配以详细的图例解释,总结相应的代码模板,同时结合例题以达到最佳的学习效果。本专栏面向算法零基础但有......
  • C++中::和:, .和->的作用和区别
    符号::和:的作用和区别::是作用域运算符,A::B表示作用域A中的-名称B,A可以是名字空间、类、结构;类作用域操作符“::”指明了成员函数所属的类。如:M::f(s)就表示f(s)是......
  • 查看磁盘容量指令及函数
       1. 查看磁盘容量指令/**************************************************************/1.df指令(1)整个文件系统有关的数据,都保存在Superblock(超级块)中,df......
  • c++常用string函数转载
    转载地址:https://blog.csdn.net/weixin_45313447/article/details/114318554?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166856136316800182722804%2522%2......
  • 函数类型指针重定义及应用
    1.使用typedef重定义函数指针类型定义一个返回值为空,无参数的函数指针类型typedefvoid(*Pfun)(void);2.使用函数指针类型定义函数指针变量PfunFUN=NUL......