模板与泛型编程

函数模板

显示实例化

模板与泛型编程_第1张图片

区别定义与声明

模板与泛型编程_第2张图片

T是模板形参 int是模板实参 

inpunt是函数形参 3是函数实参

模板与泛型编程_第3张图片

显示实例化

模板与泛型编程_第4张图片

模板必须实例化可见 翻译单元一处定义原则

与内联函数异同

引入原因:函数模板是为了编译器两个阶段的处理 内联函数是为了能在编译期展开

模板实参的类型推导

推导原则

模板与泛型编程_第5张图片

推导规则示例

1.

  • 函数形参是左值引用/指针:
    • 忽略表达式类型中的引用
    • 将表达式类型与函数形参模式匹配以确定模板实参

模板与泛型编程_第6张图片

2.万能引用:函数模板的 T&&是万能引用 

  • 如果实参表达式是右值,那么模板形参被推导为去掉引用的基本类型
  • 如果实参表达式是左值,那么模板形参被推导为左值引用,触发引用折叠

模板与泛型编程_第7张图片

引用折叠: 当有两个引用相互绑定时,它们会被折叠成一个引用。在模板实例化期间,引用折叠规则被应用于模板参数。

模板与泛型编程_第8张图片

模板与泛型编程_第9张图片3、

  • 函数形参不包含引用
    • 忽略表达式类型中的引用
    • 忽略顶层const
    • 数组、函数转换成相应的指针类型

模板与泛型编程_第10张图片

模板与泛型编程_第11张图片

无法推导的情况

模板与泛型编程_第12张图片

  • 模板实参并非总是能够推导得到
    • 如果模板形参与函数形参无关,则无法推导
    • 即使相关,也不一定能进行推导,推导成功也可能存在因歧义而无法使用

模板与泛型编程_第13张图片

即使相关,也不一定能进行推导,推导成功也可能存在因歧义而无法使用

  • 在无法推导时,编译器会选择使用缺省模板实参
    • 可以为任意位置的模板形参指定缺省模板实参——注意与函数缺省实参的区别

模板与泛型编程_第14张图片

但是缺省只能处理无法推导的情况 不能处理能推导但是有歧义的情况

模板与泛型编程_第15张图片

模板缺省实参和函数缺省实参区别

函数缺省实参要从右向左设置。 模板可以不是

模板与泛型编程_第16张图片

自动推导遇到的几种情况

1.SPFINAE

函数模板中的替换失败(Substitution Failure Is Not An Error,简称 SFINAE)是一种编译器处理模板实例化失败的机制。当尝试实例化一个模板时,如果由于某些原因导致实例化失败,编译器并不会报错,而是会尝试使用备选的模板或者进行其他处理。

报错:没有匹配的函数 、忽视函数

模板与泛型编程_第17张图片

重载函数 匹配失败就会找其他模板实例化 忽略掉前一个

模板与泛型编程_第18张图片

2.模板与非模板同时匹配,匹配等级相同,此时选择非模板的版本

下面的代码调用了非模板的fun函数

模板与泛型编程_第19张图片

注意 是匹配等级相同的情况才会选择非模板 如果不同 则会选择更加完美的匹配

下面的代码会调用模板fun

模板与泛型编程_第20张图片

3.多个模板同时匹配,此时采用偏序关系确定选择最特殊的版本

float更加特殊 所以匹配第二个fun

模板与泛型编程_第21张图片

如果同样特殊就会报错

模板与泛型编程_第22张图片

标准类型转换模板

模板与泛型编程_第23张图片

模板与泛型编程_第24张图片

尾置返回类型与类型转换

模板与泛型编程_第25张图片

模板与泛型编程_第26张图片

模板与泛型编程_第27张图片

  • 显式实例化定义:template void fun(int) / template void fun(int)

模板与泛型编程_第28张图片

  • 显式实例化声明:extern template void fun(int) / extern template void fun(int)

模板与泛型编程_第29张图片

  • 注意一处定义原则
  • 来源:stackoverflower

模板与泛型编程_第30张图片

  • 注意实例化过程中的模板形参推导

模板特化

函数模板的(完全)特化:

template<> void f(int) / template<> void f(int)

  • 并不引入新的(同名)名称,只是为某个模板针对特定模板实参提供优化算法

模板与泛型编程_第31张图片

避免使用模板的特化

模板与泛型编程_第32张图片

  • 不参与重载解析,会产生反直觉的效果
  • 通常可以用重载代替
  • 一些不便于重载的情况:无法建立模板形参与函数形参的关联

模板与泛型编程_第33张图片

1.if constexpr解决

 模板与泛型编程_第34张图片

2.假函数解决

模板与泛型编程_第35张图片

函数模板不能偏特化

C++20auto定义模板参数

模板与泛型编程_第36张图片

模板与泛型编程_第37张图片

类模板

模板与泛型编程_第38张图片

成员函数只有在调用时才会被实例化

证明:程序无法编译 但是去掉21行就能编译

模板与泛型编程_第39张图片

  • 类内类模板名称的简写

模板与泛型编程_第40张图片

类模板成员函数类外定义

模板与泛型编程_第41张图片

成员函数模板

类的成员函数模板

模板与泛型编程_第42张图片

模板与泛型编程_第43张图片

类模板的成员函数模板

模板与泛型编程_第44张图片

类内定义

模板与泛型编程_第45张图片

类外定义

模板与泛型编程_第46张图片

友元函数模板

模板与泛型编程_第47张图片

fun是个友元函数模板

模板与泛型编程_第48张图片

模板与泛型编程_第49张图片

类模板的静态成员

模板与泛型编程_第50张图片

成员对象只有类实例化的时候才会被实例化

static函数只有使用的时候才会实例化

C++11模板参数为友元

模板与泛型编程_第51张图片

类模板的实例化

模板与泛型编程_第52张图片

模板与泛型编程_第53张图片

类模板的显示实例化

类模板特化

完全特化

模板与泛型编程_第54张图片

偏特化

模板与泛型编程_第55张图片

模板与泛型编程_第56张图片

C++17类模板的实参推导

B x(3)自动推导成B

模板与泛型编程_第57张图片

模板与泛型编程_第58张图片

pair的自动推导

模板与泛型编程_第59张图片

C++17之前的解决方法 函数模板推导

模板与泛型编程_第60张图片

C++20新概念

  • 模板的问题:没有对模板参数引入相应的限制
    • 参数是否可以正常工作,通常需要阅读代码进行理解
    • 编译报错友好性较差(vector)

  • (C++20)Concepts:编译期谓词,基于给定的输入,返回truefalse
    • constraintsrequire从句)一起使用时限制模板参数
    • 通常置于表示模板形参的尖括号后面进行限制

此处限制T是int和float

模板与泛型编程_第61张图片

优点:报错一目了然

模板与泛型编程_第62张图片

Concept的定义与使用

  • 1.包含一个模板参数的Concept
    • 使用requires从句
    • 直接替换typename

模板与泛型编程_第63张图片

模板与泛型编程_第64张图片

  • 2.包含多个模板参数Concept
    • 用做类型constraint时,少传递一个参数,推导出的类型将作为首个参数

模板与泛型编程_第65张图片

requires 表达式 (C++20 起) - cppreference.com   参考资料

模板与泛型编程_第66张图片

模板与泛型编程_第67张图片

  • 简单表达式:表明可以接收的操作
  • 模板与泛型编程_第68张图片

模板与泛型编程_第69张图片

  • 类型表达式:表明是一个有效的类型

模板与泛型编程_第70张图片

模板与泛型编程_第71张图片

模板与泛型编程_第72张图片

  • requires从句所引入的限定具有偏序特性,系统会选择限制最严格的版本

如下 C1更严格 编译器选择匹配C1

模板与泛型编程_第73张图片

  • 特化小技巧:在声明中引入“A||B”进行限制,之后分别针对AB引入特化

模板与泛型编程_第74张图片

模板的实现原理

模板需要编译两次,在第一次编译时仅仅检查最基本的语法,比如括号是否匹配。等函数真正被调用时,才会真正生成需要的类或函数。

所以这直接导致了一个结果,就是不论是模板类还是模板函数,声明与实现都必须放在同一个文件中。因为在程序在编译期就必须知道函数的具体实现过程。如果实现和声明分文件编写,需要在链接时才可以看到函数的具体实现过程,这当然会报错。

于是人们发明了.hpp文件来存放模板这种声明与实现在同一文件的情况。

模板与泛型编程_第75张图片

模板与泛型编程_第76张图片

重载与模板

模板与泛型编程_第77张图片

模板与泛型编程_第78张图片

模板与泛型编程_第79张图片

数值模板参数与模板模板参数

模板与泛型编程_第80张图片

模板可以接受编译器常量为模板参数

模板与泛型编程_第81张图片

C++17auto value

·模板与泛型编程_第82张图片

C++20支持浮点数作为模板参数(clang 12不支持)

接受模板作为模板参数

模板与泛型编程_第83张图片

C++17模板的模板考虑缺省实参

模板与泛型编程_第84张图片

clang12支持有限

别名模板

模板与泛型编程_第85张图片

  • 为目标本身引入别名

模板与泛型编程_第86张图片

  • 为类模板的成员引入别名

模板与泛型编程_第87张图片

  • 别名模板不支持特化,但可以基于类模板的特化引入别名,以实现类似特化的功能
    • 注意与实参推导的关系

模板与泛型编程_第88张图片

变长模板

变长模板(Variadic Template)

  • 变长模板参数与参数包

形参包(parameter pack)是C++中用于处理可变数量参数的一种特性。形参包可以接受任意数量的模板参数,并在模板中进行处理。

形参包的基本语法是使用...来表示,可以用在函数模板、类模板以及别的模板上下文中。形参包的展开可以通过递归、折叠表达式(C++17引入)等方式进行。

模板与泛型编程_第89张图片

接数值

模板与泛型编程_第90张图片

接类型

模板与泛型编程_第91张图片

带可选名字的函数形参包

模板与泛型编程_第92张图片

模板与泛型编程_第93张图片

完美转发

模板与泛型编程_第94张图片

右值引用失效

模板与泛型编程_第95张图片

使用万能引用 T&&情况

还是失效 原因:右值引用是个左值

模板与泛型编程_第96张图片

解决方法:转发 forward

  • (C++11)完美转发:std::forward函数
    • 通常与万能引用结合使用
    • 同时处理传入参数是左值或右值的情形

模板与泛型编程_第97张图片

包展开与折叠表达式

模板与泛型编程_第98张图片

消除歧义

internal*p被编译器解读成乘法

模板与泛型编程_第99张图片

  • 使用typenametemplate消除歧义
    • 使用typename表示一个依赖名称是类型而非静态数据成员

模板与泛型编程_第100张图片

使用template表示一个依赖名称是模板

T::internal

模板与泛型编程_第101张图片

使用template表示一个依赖名称是模板

模板与泛型编程_第102张图片

template与成员函数模板调用

internal被解读为依赖obj <被解读成小于号

模板与泛型编程_第103张图片

解决方法

模板与泛型编程_第104张图片

C++14变量模板

  • (C++14)变量模板
    • template T pi = (T)3.1415926;
    • 其他形式的变量模板

C++14 引入了变量模板(Variable Templates)的概念,它允许你定义参数化的变量,类似于函数模板允许你定义参数化的函数。变量模板提供了一种通用的方式来定义与类型相关的常量或变量,使得代码更具有通用性和灵活性。

模板与泛型编程_第105张图片

lambda模板表达式

// 使用 C++20 中的 lambda 模板
    auto genericLambda = [](T x, T y) {
        return x + y;
    };

练习

#include 
#include 
#include 
#include 

template
struct get_type{
    using type=T;
};

template
struct get_type{
    using type=T;
};

template
class myArray{
    using iterator=T*;
    using const_iterator=const T*;
public:
    myArray(size_t count);
    ~myArray();
    myArray(const std::initializer_list& list);
    myArray(std::initializer_list& list);


    iterator begin(){
        return _data;
    }
    const_iterator cbegin()const{
        return _data;
    }
private:
    T* _data;
};

template
myArray::myArray(size_t count) {
    if(count){
        _data=new T[count];
    }
    else{
        _data= nullptr;
    }
}

template
myArray::~myArray() {
    if (_data) {
        delete[] _data;
    }
}

template
myArray::myArray(const std::initializer_list& list){
    if(int count=0;list.size()){
        _data=new T[list.size()]();
        if constexpr (std::is_pointer_v){  //萃取 如果是指针就要深拷贝
            for(auto e:list)
              _data[count++]=new typename get_type::type(*e);
        }
        else{
            for(const auto& e:list){
                _data[count++]=e;
            }
        }
    }
    else{
        _data= nullptr;
    }
}

你可能感兴趣的:(c++,开发语言)