函数模板-C11/17/14

news2025/1/17 5:56:18

函数模板

文章目录

  • 函数模板
    • 定义函数模板
    • 使用函数模板
          • 样例
    • 两阶段翻译 Two-Phase Translation
      • 模板的编译和链接问题
    • 多模板参数
      • 引入额外模板参数作为返回值类型
      • 让编译器自己找出返回值类型
      • 将返回值声明为两个模板参数的公共类型
          • 样例
    • 默认模板参数
          • 样例
    • 重载函数模板
    • 模板函数特化
    • 非类型模板参数

定义函数模板

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

使用函数模板

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同理。

样例

// 函数模板的推断

#include <iostream>
using namespace std;
template <typename T>
void Function(T arg)
{
    cout << "template<T>   " << arg << endl;
}
void Function(int arg)
{
    cout << "oridary <int> " << arg << endl;
}
int main(int argc, char **argv)
{
    // oridary <int> 13
    // template<T>   123
    // template<T>   13
    // template<T>   13
    Function(13);
    // 当普通函数和函数模板都符合时,普通函数的优先级更高
    Function("123");

    Function<int>(13); // 可以强制使用模板
    Function<>(13);    // 可以自动推导
}

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

img

两阶段翻译 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

}

img

模板的编译和链接问题

大多数人会按照如下方式组织非模板代码: 将类或者其他类型声明放在头文件(.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源文件,就是把类的声明和定义都放在了一个文件中。


多模板参数

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

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

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

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

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

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里很常见。

img

样例

// /// <summary>
// ///引入额外模板参数作为返回值类型
// 让编译器自己找出返回值类型
// 将返回值声明为两个模板参数的公共类型,比如int和float,公共类型就是float
#include <iostream>
using namespace std;
namespace test01
{
    template <typename RT, typename T2, typename T>
    RT add(const T2 &x, const T &y)
    {
        return x + y;
    }
}
namespace test02
{
    template <typename T2, typename T>
    auto add(const T2 &x, const T &y) -> decltype(x + y)
    {
        return x + y;
    }
}
namespace test03
{
    template <typename T2, typename T>
    typename std::common_type<T2, T>::type add(const T2 &x, const T &y)
    {
        return x + y;
    }

}
namespace test04
{
    template <typename T2, typename T>
    std::common_type_t<T2, T> add(const T2 &x, const T &y)
    {
        return x + y;
    }

}
int main(int argc, char **argv)
{
    /// @brief 引入额外模板参数作为返回值类型
    /// @param argc
    /// @param argv
    /// @return
    cout << test01::add<float, float, float>(11.2, 22.2) << endl;
    cout << test01::add<int, float, float>(11.2, 22.2) << endl;
    /// @brief 让结果自己推断返回值,让编译器自己找出返回值类型
    /// @param argc
    /// @param argv
    /// @return
    cout << test02::add<float, int>(22.5, 35) << endl;
    cout << test02::add<int, int>(22, 35) << endl;
    /// @brief 将返回值声明为两个模板参数的公共类型
    /// @param argc
    /// @param argv
    /// @return
    cout << test03::add<float, int>(22.5, 35) << endl;
    cout << test03::add<int, int>(22, 35) << endl;
    /// @brief 这里使用_t后缀让我们不用写typename和::type
    /// @param argc
    /// @param argv
    /// @return
    cout << test04::add<float, int>(22.5, 35) << endl;
    cout << test04::add<int, int>(22, 35) << endl;
}

默认模板参数

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

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,因为其被显式指定
样例
#include <iostream>
using namespace std;
namespace test01
{
    template <typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>
    RT max(T1 a, T2 b)
    {
        return b < a ? a : b;
    }

}
namespace test02
{
    template <typename RT = long, typename T1, typename T2> //
    RT max(T1 a, T2 b)
    {
        return b < a ? a : b;
    }

}
int main(int argc, char **argv)
{
    /// @brief  函数模板的默认参数
    /// @param argc
    /// @param argv
    /// @return
    auto a = test01::max(4, 7.2);
    auto b = test01::max<double, int, long double>(7.2, 4);
    auto c = test02::max(4, 7.2);
    auto d = test02::max<double, int, long double>(7.2, 4);
    cout << a << " " << b << endl;
    cout << c << " " << d << endl;
}

重载函数模板

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

#include <iostream>
using namespace std;
template <typename T>
T Max(T a, T b)
{
    std::cout << "Max<T,T>()\n";
    return b < a ? a : b;
}
#if 1

template <typename T>
T Max(T a, T b, T c)
{
    cout << "Max<T,T,T>()" << endl;
    return Max(Max(a, b), c);
}

#endif // !1
template <typename T>

T Max(T *a, T *b)
{

    cout << "Max<T*>()\n";
    return (*b) < (*a) ? (*a) : (*b);
}
int main()
{
    /// @brief 函数模板的重载
    /// @return
    cout << Max<>(15, 5) << endl;
    int a = 55, b = 99;
    cout << Max<>(&a, &b) << endl;
    cout << Max<>(25, 23, 78) << endl;
}

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

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

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

模板函数特化

有时通用的函数模板不能解决个别类型的问题,我们必须对此进行定制,这就是函数模板的特化。函数模板的特化必须把所有的模版参数全部指定。

#include <iostream>
using namespace std;
/// @brief 函数模板的全特化
/// @param argc
/// @param argv
/// @return
namespace internal
{
    template <typename T, typename U>
    auto func(T arg, U arg2)
    {
        return arg + arg2;
    }
    template <>
    auto func(string x, string y)
    {
        return x + y;
    }
    /// 函数模板的全特化是根据函数模板来的,所以参数不能多或少(但类型可以变化),
    // 但是函数体是可以改变的

    // template <>
    // auto func(string x, string y, string z)
    // {
    //     return x + y + z;
    // }
    // template<>
    // auto func(string x)
    // {
    //     return x ;
    // }
    auto func(string x, int y)
    {
        return new string(x, y);
    }
    auto func(int x, int y)
    {
        return new string(x, y);
    }

}
int main(int argc, char **argv)
{
    cout << internal::func(20, 30) << endl;
    cout << internal::func("test", 30) << endl;

    cout << internal::func(string("test"), string("Code"));
}

非类型模板参数

因为T前边有一个typename/calss ,这表示T代表一个类型,是一个类型参数。

那么在模板参数列表里边,还可以定义非类型参数;非类型参数代表的是一个值。

既然非类型参数代表一个值,那么我们肯定不能用typename/class这种关键字来修饰这个值。

我们当然要用以往学习过的传统类型名来指定费类型参数。比如你非类型参数S,如果是个整型,那么就用 int s。

当模板被实例化时,这种非类型模板参数的值,或者是用户提供的,或者是编译器推断的,都有可能。

但是这些值都必须是常量表达式。因为实例化这些模板实在编译器编译时进行的。

template <int a, int b>
int func2()
{
    int he = a + b;
    return he;
}


int result = func2<2, 3>(); //显示指定模板参数——用<>提供额外信息

int i = 12;
int result2 = func2<i, 3>(); //不可以,报错,必须给定编译时就能确定的值这里i是在运行 时才能确定的值

实例化模板实在编译时进行的不是运行时

template <typename T, int a, int b>
int func3(T c)
{
    int result = (int)c + a + b;
    return result;
}


int i = func3<int, 11, 12>(13);

int i = func3<double, 11, 12>(13); //这里系统会以<>传递的类型为准,将13转成double型

这里 L1与L2的值是编译器推断出来的

template <unsigned L1, unsigned L2>
int charscomp(const char (&p1)[L1], const char (&p2)[L2])
{
    return strcmp(p1, p2);
}

int result3 = charscomp("test1","test"); //没有提供费类型模板参数,系统会根据test1 
                                         //的长度6个,test的长度5个,来取代L1,L2

模板函数可以是内联函数

template <typename T, int a, int b>
inline
int func3(T c)
{
    int result = (int)c + a + b;
    return result;
}

模板定义并不会导致编译器生成代码,只有在我们调用这个函数模板时,使用编译器为我们实例化了一个特定版本的函数之后编译器才会生成代码。
编译器生成代码的时候,需要能够找到函数的函数体,所以,函数模板的定义通常都是在 .h 中


这里 L1与L2的值是编译器推断出来的

template <unsigned L1, unsigned L2>
int charscomp(const char (&p1)[L1], const char (&p2)[L2])
{
    return strcmp(p1, p2);
}

int result3 = charscomp("test1","test"); //没有提供费类型模板参数,系统会根据test1 
                                         //的长度6个,test的长度5个,来取代L1,L2

模板函数可以是内联函数

template <typename T, int a, int b>
inline
int func3(T c)
{
    int result = (int)c + a + b;
    return result;
}

模板定义并不会导致编译器生成代码,只有在我们调用这个函数模板时,使用编译器为我们实例化了一个特定版本的函数之后编译器才会生成代码。
编译器生成代码的时候,需要能够找到函数的函数体,所以,函数模板的定义通常都是在 .h 中


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

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

相关文章

cocoapods的使用

swift开发之cocoapods的使用 之前介绍了cocoapods的使用&#xff0c;我们可以知道通过pod search XXX(三方依赖库名称)可以就搜索到想要的第三方是否存在。 这次主要简单介绍cocoapods如何引入第三方库的,以BluetoothKit为例。 首先&#xff0c;我们终端中通过cd命令定位到要…

二十二、shiro安全框架基础

一、简介 1. shiro简介 Apache Shiro 是 Java 的一个安全&#xff08;权限&#xff09;框架。Shiro 可以非常容易的开发出足够好的应用&#xff0c;其不仅可以用在JavaSE 环境&#xff0c;也可以用在 JavaEE 环境。Shiro 可以完成&#xff1a;认证、授权、加密、会话管理、与…

“智慧”控漏 削减产销差-城镇供水管网分区计量管理系统

平升电子城镇供水管网分区计量管理系统根据国际国内分区计量的要求和标准研发&#xff0c;专门针对水司漏损控制和产销差管理而设计。系统涵盖分区管理、管网流量和压力监控、水量统计分析、产销差分析、漏损评估、夜间最小流量分析、用水异常报警等功能。核心目标是找到整个管…

ReactJS入门

目录 一&#xff1a;前端开发的演变 二&#xff1a;ReactJS简介 三&#xff1a;搭建环境 四&#xff1a;React快速入门 一&#xff1a;前端开发的演变 到目前为止&#xff0c;前端的开发经历了四个阶段&#xff0c;目前处于第四个阶段。这四个阶段分别是&#xff1a; 阶段一…

equals()与hashcode()之间的关系

1、equals简介 被用来检测两个对象是否相等&#xff0c;即两个对象的内容是否相等&#xff1b; equals 方法&#xff08;是String类从它的超类Object中继承的&#xff09;用于比较引用和比较基本数据类型时具有不同的功能&#xff1a; 比较基本数据类型&#xff0c;如果两个值…

马哥SRE第11周课程作业

ansible role zabbix相关话题1. ansible 常用指令总结&#xff0c;并附有相关示例。1.1 Ansible相关工具1.1.1 ansible-doc1.1.2 ansible 命令用法1.1.3 ansible-console1.1.4 ansible-playbook1.1.5 ansible-vault1.1.5 ansible-galaxy2. 总结ansible playbook目录结构及文件用…

javaee之Spring4

之前说到AccountDao需要继承JdbcDaoSupport这个类&#xff0c;那么现在来看一下这个类的内容 JdbcDaoSupport.java package com.itheima.dao.impl;/*** 此类用于抽取dao中的重复代码 */public class JdbcDaoSupport {private JdbcTemplate jdbcTemplate;public void setJdbcT…

人大金仓数据库备份应用sys_dump的使用

人大金仓数据库软件给数据库管理员用户提供了管理维护数据库的多个客户端应用&#xff0c;更多参考&#xff1a;《KingbaseES客户端应用参考手册》。 我们可以看到备份的应用有两个&#xff1a; 1、sys_dump:将KingbaseES数据库备份为一个脚本文件或者其他归档文件 2、sys_d…

表单校验重要性和多规则校验

表单校验分类 校验位置&#xff1a; 客户端校验 服务端校验 表单校验框架 JSR&#xff1a;java规范提案 303&#xff1a;提供bean属性相关校验规则 JCP:java社区 Hibernate框架中包含一套独立的校验框架hibernate-validator 实际的校验规则 同一个字段有多个约束条件 引用…

股权转让项目:沈阳派尔化学有限公司55%股权转让

股权转让项目&#xff1a;沈阳派尔化学有限公司55%股权转让&#xff1b;该项目由 广州产权交易所 发布&#xff0c;于2022年12月25日被塔米狗平台收录。 该公司在 2021 年最新一期财务报告中&#xff0c; 披露的资产总额&#xff08;万元&#xff09;&#xff1a;7148.98 &…

装修半包包括哪些内容呢?极家精工装修好不好

​装修半包包括哪些内容呢&#xff1f;极家精工装修好不好。在装修房子的时候&#xff0c;很多人都会选择半包装修&#xff0c;因为可以自己挑选材料&#xff0c;自己跟工程比较放心。另外一边比较重要的原因就是能省钱&#xff0c;对于预算有限的小伙伴真的再适合不过啦&#…

唐玄奘把 「JWT 令牌」玩到了极致

唐玄奘把 「JWT 令牌」玩到了极致 你好&#xff0c;我是悟空。 西游记的故事想必大家在暑假看过很多遍了&#xff0c;为了取得真经&#xff0c;唐玄奘历经苦难&#xff0c;终于达成。 在途经各国的时候&#xff0c;唐玄奘都会拿出一个通关文牒交给当地的国王进行盖章&#x…

基于线性表的图书管理系统(java)

目录 1、简介 2、代码 &#xff08;1&#xff09;ManageSystem类 &#xff08;2&#xff09;book类 3、测试程序运行结果截图 &#xff08;1&#xff09;登录和创建 &#xff08;2&#xff09;输出 &#xff08;3&#xff09;查找 &#xff08;4&#xff09;插入 &a…

如何用乐高积木式操作让 ChatGPT 变得更强大?

需求这些日子&#xff0c;很多小伙伴儿玩儿 ChatGPT 不亦乐乎&#xff0c;甚至陷入了沉迷。他们尝试了各种 ChatGPT 的功能。不少功能强悍到不可思议&#xff1b;当然&#xff0c;也有些功能尝试因遇到障碍无法完成。于是很多用户非常失望&#xff0c;觉得 ChatGPT 好像啥都干不…

20221227:Rockchip-RK模型转换

Tips: 不同芯片对应的NPU和toolkit是不同的,注意区分! 平台 RK1808/RK1806 RV1109/RV1126 RKNPU:本工程主要为Rockchip NPU提供驱动、示例等。 GitHub - rockchip-linux/rknpuContribute to rockchip-linux/rknpu development by creating an account on GitHub.https://gi…

小程序项目开发

目录 一&#xff0c;flex弹性布局 1.什么是flex布局&#xff1f; 2.flex属性 3.视图层 View WXML 1数据绑定 2.列表渲染 3.条件渲染 4.模板 5. 数据处理 二&#xff0c;轮播图--组件的使用 1.WXSS 样式导入 内联样式 选择器 全局样式与局部样式 WXS 页面渲染 三&…

zabbix常用监控项解读

CPU来源模板&#xff1a;Template Module Linux CPU by Zabbix agent 内存&#xff08;memory&#xff09;来源模板&#xff1a;Template Module Linux memory by Zabbix agent 磁盘空间&#xff08;disk&#xff09; 数据来源&#xff1a;Get /proc/diskstats 监控项原型&am…

【小5聊】ElementUI-Vue3-TS项目简单创建

vue2升级到vue3&#xff0c;不管任何框架&#xff0c;升级总有它改进的地方和原因&#xff0c;否则升级就毫无意义&#xff0c;技术变化日新月异&#xff0c;必须保持与时俱进&#xff0c;否则就很容易在技术的浪潮中被淘汰&#xff01; vue3相比以前版本&#xff0c;最大一个变…

PyTorch笔记 - Normalization Layer (Batch\Layer\Instance\Group\Weight)

欢迎关注我的CSDN:https://blog.csdn.net/caroline_wendy 本文地址:https://blog.csdn.net/caroline_wendy/article/details/128416962 Normalization in NN: Batch Normalization: per channel across mini-batchtorch.nn.BatchNorm1d / torch.nn.BatchNorm2dLayer Normaliz…

Hive+Spark离线数仓工业项目--数仓维度层DWS层构建(1)

维度建模回顾&#xff1a;建模流程 目标&#xff1a;掌握维度建模的建模流程 实施 step1-需求调研&#xff1a;业务调研和数据调研 - 了解整个业务实现的过程 - 收集所有数据使用人员对于数据的需求 - 整理所有数据来源 step2-划分主题域&#xff1a;面向业务将业务…