ch13-模板

  1. 函数模板
  2. 类模板与成员函数模板
  3. Concepts
  4. 模板相关内容

一.函数模板

1.1.使用 template 关键字引入模板

使用 template 关键字引入模板: template void fun(T) {...}

  • 函数模板的声明与定义
  • typename 关键字可以替换为 class ,含义相同

    : typename == class ```c #include //没有大括号里面的实现:函数模板的声明 template void fun(T);

  • <>:尖括号里面包含的是函数模板的模板参数,这里重点套路的是类型模板参数:typename T
  • typename:
  • T:类型模板的形式参数,简而言之:T为模板形参,编译期间赋予实参
  • input:函数形参,运行期调用 template void fun(T input){ //有大括号这部分的实现:函数模板的定义 std::cout<<input<<std::endl;

}

int main(){ }


##  1.2.函数模板的显式实例化: fun<int>(3)
- 实例化会使得编译器产生相应的函数(函数模板并非函数,不能调用)
- ==函数模板中包含了两对参数:函数形参 / 实参;模板形参 / 实参,调用是两个形参都要对应赋值,一个使用尖括号<>,一个使用圆括号()==
```c
#include<iostream>

// template<class T>
template<typename T>
void fun(T input){
  std::cout<<input<<std::endl;

}

int main(){
  fun<int>(3);//显示实例化
  - T : 模板形参
  - int : 模板实参(用int 代替T)
  - T <---> int    模板的形参<--->实参
  - input : 函数形参
  - 3 : 函数实参(用3 代替 input)
  - input<--->3    函数的形参<--->实参
}
  • 编译期的两阶段处理
    • 模板语法检查
    • 模板实例化
#include<iostream>
struct Str(){

};
// template<class T>
template<typename T>
void fun(T input){
  std::cout<<input<<std::endl;
}

int main(){
  fun<Str>(Str{});//模板实例化的时候报错,语法没错,因为Str没有定义如何打印输出,也就是说在实例化的时候,系统会回过头去再次检查模板函数,这样就需要模板函数的定义与实例化在同一翻译单元可见.
}
  • 模板必须在实例化时可见 —— 翻译单元的一处定义原则 ```c //header.h #include

// template template void fun(T input){ std::cout<<input<<std::endl; }//这个函数不会报错,C++做了扩展,在头文件进行实现:在翻译单元可见

void normal_fun(){ }//链接错误:source.cpp和main.cpp都包含了这个头文件,header没加#ifndef,,,这个操作,会被重复定义

//source.cpp #include #include

void g(){ fun(100); } //main.cpp

#include #include

int main(){ fun(3); }


- 注意与内联函数的异同

内联函数与模板函数均需要在头文件中进行实现,但是这样做的原因不同:内联函数是便于在调用他的地方展开,模板函数是需要在同一翻译单元可见.


##  1.3.函数模板的重载

```c
#include<iostream>

// template<class T>
template<typename T>
inline void fun(T input){
  std::cout<<input<<std::endl;
}

//这就是函数模板的重载,函数名均为fun()
template<typename T1, typename T2>
inline void fun(T1 input1, T2 input2){
  std::cout<<input1<<std::endl;
  std::cout<<input2<<std::endl;
}

//这也是函数模板的重载,函数参数列表不同:形参类型不同
template<typename T>
inline void fun(T* input){
  std::cout<<*input<<std::endl;
}
int main(){
  int x = 3;
  fun<int>(&x);
}

1.4.模板实参的类型推导

模板实参的类型推导参考文献. 可以使用cpp insight进行查看具体的实例化是怎样的 隐式实例化

#include<iostream>
// template<class T>
template<typename T>
inline void fun(T input){
  std::cout<<input<<std::endl;
}
int main(){
  fun(3);//3,同样可以打印出3,这里实际上就有隐式实例化的过程
  - 根据函数实参3,进行模板实参类型的推导
}
  • 如果函数模板在实例化时没有显式指定模板实参,那么系统会尝试进行推导

  • ==推导是基于函数实参(表达式)确定模板实参的过程==,其基本原则与 auto 类型推导相似

    • 函数形参是左值引用 / 指针:
      • 忽略表达式类型中的引用
      • 将表达式类型与函数形参模式匹配以确定模板实参 ```c #include // template template inline void fun(T& input){//函数形参是左值引用 std::cout<<input<<std::endl; } int main(){ int y = 3; int& x = y;//推导过程首先忽略int& 这个引用 fun(x);
        • T : int& –> int } ```
    • 函数形参是==万能引用(既可以引用左值也可以引用右值)==
      • 如果实参表达式是右值,那么模板形参被推导为去掉引用的基本类型
      • 如果实参表达式是左值,那么模板形参被推导为左值引用,触发引用折叠: int& && -> int&
        #include<iostream>
        // template<class T>
        template<typename T>
        inline void fun(T&& input){//函数形参是万能引用(既可以引用左值,也可以引用右值),不是右值引用
          std::cout<<input<<std::endl;
        }
        int main(){
          fun(3);
          int&& x=3;
        }
        
    • 函数形参不包含引用,==其实推导会更复杂==
      • 忽略表达式类型中的引用
      • 忽略顶层 const
      • 数组、函数转换成相应的指针类型 ```c #include // template template inline void fun(T input){ std::cout<<input<<std::endl; } int main(){ int x = 3; const int& ref = x; fun(ref);
        • T: const int& –> const int –> int } c #include // template template inline void fun(T input){ } int main(){ int x = 3; const int* const ptr = &x; fun(ptr);
        • T: const int* const –> const int* } c #include // template template inline void fun(T input){ } int main(){ int x[3] ; fun(x);
        • T: int[3] –> int* } c #include // template template<typename T, typename T2> inline void fun(T input, T2 input2){ } int main(){ fun(3,5);
        • 也是合法的,int匹配第一个模板形参,此时既有显示实例化也有隐式实例化 } ```

1.5.模板实参并非总是能够推导得到

  • 如果模板形参与函数形参无关,则无法推导

    #include<iostream>
    // template<class T>
    template<typename T,typename Res>
    Res fun(T input){
    }
    int main(){
      fun(3);//无法推导返回类型
    }
    
  • 即使相关,也不一定能进行推导,推导成功也可能存在因歧义而无法使用

    #include<iostream>
    // template<class T>
    template<typename T>
    void fun(unsigned input = sizeof(T)){
    }
    int main(){
      fun(3);//无法推导
    }
    

    ```c #include #include // template template void fun(typename std::remove_reference::type input){

    • std::remove_reference::type input 去掉引用的功能 } int main(){ fun(3);//无法推导 } ```

1.6.在无法推导时,编译器会选择使用缺省模板实参

  • 可以为任意位置的模板形参指定缺省模板实参 —— 注意与函数缺省实参的区别

    #include<iostream>
    #include<type_traits>
    // template<class T>
    template<typename T = int>//缺省模板实参
    void fun(typename std::remove_reference<T>::type input){
      - std::remove_reference<T>::type input 去掉引用的功能
    }
    int main(){
      fun(3);//合法,具有缺省模板实参
    }
    
    #include<iostream>
    #include<type_traits>
    void f(int x = 4, int y){}//无法编译,最右边的都具有缺省实参才可以
    - 但模板函数的缺省实参不同
    // template<class T>
    template<typename Res = double, typename T>//区别函数缺省,这里是合法的
    Res fun(T x,T y){
    }
    int main(){
      fun(3, 5);//合法
    }
    

    1.7.显式指定部分模板实参

  • 显式指定的模板实参必须从最左边开始,依次指定
  • 模板形参的声明顺序会影响调用的灵活性

    #include<iostream>
    #include<type_traits>
    template<typename T1, typename Res = double, typename T2>//区别函数缺省,这里是合法的
    Res fun(T1 x,T2 y){
    }
    int main(){
      fun<int,int>(3, 5);//合法,显示指定部分模板实参:T1=int,Res=int,T2自动推导
    }
    

1.8.函数模板制动推导时会遇到的几种情况

  • 函数形参无法匹配 —— SFINAE (替换失败并非错误)

    #include<iostream>
    #include<type_traits>
    template<typename T>
    Res fun(T x,T y){
    }
    int main(){
      fun(3, 5.0);//匹配失败,没有其他匹配的模板函数:替换失败并非错误
    }
    
  • 模板与非模板同时匹配,匹配等级相同,此时选择非模板的版本
    #include<iostream>
    #include<type_traits>
    template<typename T>
    Res fun(T x,T y){
      std::cout<<1<<std::endl;
    }
    
    void fun(int x, double y){
      std::cout<<2<<std::endl;
    }
    int main(){
      fun(3, 5.0);//2, 虽然是同级别匹配,但是系统会选择非模板-->完美匹配
    }
    
  • 多个模板同时匹配,此时采用偏序关系确定选择 “ 最特殊 ” 的版本

    #include<iostream>
    #include<type_traits>
    template<typename T>
    Res fun(T x,T y){
      std::cout<<1<<std::endl;
    }
    
    template<typename T>
    Res fun(T x,float y){
      std::cout<<2<<std::endl;
    }
    
    int main(){
      fun(3, 5.0f);//2, 选择 “ 最特殊 ” 的版本
    }
    

1.9.函数模板的实例化控制

  • 显式实例化定义: template void fun(int) / template void fun(int)</font> ```c //header.h #include #include template Res fun(T x){ std::cout<<x<<std::endl; } template//注意这里没有尖括号 void fun(int x);//显示实例化定义:T = int template//注意这里没有尖括号 void fun(int x);//同样是显示实例化定义:T = int - 在外部调用时,就可以直接调用预先实例化好的模板函数 //mian.cpp - 在生成库文件进行使用时,若模板函数被封装成了库,则无法进行动态实例化?需要对函数模板进行显示实例化定义,但暂时不调用 #include "header.h" int main(){ int x = 3; fun(x);//显式实例化 } ```
  • 显式实例化声明: extern template void fun(int) / extern template void fun(int)</font> ```c #include template Res fun(T x){ std::cout<<x<<std::endl; } extern template//注意这里没有尖括号,显示实例化声明 void fun(int x); int main(){ int x = 3; fun(x);//显式实例化 } ```
  • 注意一处定义原则(video 89-4-21.00)
  • 注意实例化过程中的模板形参推导(video 89-4-21.00)

1.10.函数模板的 ( 完全 ) 特化

函数模板的 ( 完全 ) 特化: template<> void f(int) / template<> void f(int) -->注意区别前面的显示实例化定义和声明,此处有尖括号<></font> - 并不引入新的(同名)名称,只是为某个模板针对特定模板实参提供优化算法(==特化:模板函数无法满足算法需求,但是可以根据模板函数就行扩展更改==) - 注意与重载的区别 - 注意特化过程中的模板形参推导 ```c #include template void fun(T x){ //... } template void fun(T* x){//函数模板重载 //... } template<>//不会引入同名名称,只是模板函数的一个特例 void fun(int){ } int main(){ } ``` ## 1.10.避免使用函数模板的特化-video-90-2 避免使用函数模板的特化[参考资料](https://www.youtube.com/watch?v=GydNMuyQzWo&list=PLHTh1InhhwT6DdPY3CPxayypP5DXek_vG&index=8). ![](/CPP-Note/assets/images/ch13/01.png) - 不参与重载解析,会产生反直觉的效果(video-90-2-3.00) - 通常可以用重载代替函数模板的特化 - 一些不便于重载的情况:无法建立模板形参与函数形参的关联 - 使用 if constexpr 解决 - 引入 “ 假 ” 函数形参 - 通过类模板特化解决 ## 1.11.(C++20) 函数模板的简化形式 函数模板的简化形式:使用 auto 定义模板参数类型 - 优势:书写简捷 - 劣势:在函数内部需要间接获取参数类型信息 ```c #include #include template void fun(T x){ //... } //注意这是C++ 20的新特性,则就表示的是模板函数 void fun(auto x){ //... } int main(){ int x; fun(&x); } ``` # 二. 类模板与成员函数模板 ## 2.1.使用 template 关键字引入模板 使用 template 关键字引入模板: template class B {...}; ```c #include #include template class B {//类模板定义 }; template class B;//类模板声明 int main(){ B x; B y; } ``` - 类模板的声明与定义 —— 翻译单元的一处定义原则 - 成员函数只有在调用时才会被实例化 ```c #include #include template class B {//类模板定义 public: void fun(T input){ std::cout<< input<< std::endl; } }; template class B;//类模板声明 int main(){ B x; x.fun(3); } ``` - 类内类模板名称的简写 ```c #include #include template class B {//类模板定义 public: auto fun(T input){ return B{}; //return B{};//可以这样简写 } }; int main(){ B x; x.fun(); } ``` - 类模板成员函数的定义(类内、类外) ```c #include #include template class B {//类模板定义 public: void fun(){ } }; template void B::fun(){ //类模板的成员函数在类外定义方式 } int main(){ B x; x.fun(); } ``` ## 2.2.成员函数模板 - 类的成员函数模板 ```c #include #include class B {//类模板定义 public: template void fun(){//类内定义 } }; int main(){ B x; x.fun(); } ``` ```c #include #include class B {//类模板定义 public: template void fun(); }; template void B::fun(){ //类的成员函数模板在类外定义方式 } int main(){ B x; x.fun(); } ``` - 类模板的成员函数模板 ```c #include #include template class B {//类模板定义 public: template void fun(){}//内类定义 }; int main(){ B x; x.fun(); } ``` ```c #include #include template class B {//类模板定义 public: template void fun(); }; template template void B::fun(){ //内外定义 } int main(){ B x; x.fun(); } ``` ## 2.3.友元函数(模板) - 可以声明一个函数模板为某个类(模板)的友元 ```c #include #include template void fun(); template class B { template friend void fun(){ } int x; }; template void fun(){ //定义 B tmp1; tmp1.x; } int main(){ fun(); } ``` - C++11 支持声明模板参数为友元--用处不是那么大 ## 2.4.类模板的实例化 [类模板的实例化](https://en.cppreference.com/w/cpp/language/class_template). - 与函数实例化很像 - 可以实例化整个类,或者类中的某个成员函数 ## 2.5.类模板的(完全)特化 / 部分特化(偏特化)--重要 - 特化版本与基础版本可以拥有完全不同的实现 ```c #include #include template class B { void fun(){ std::cout<<"1\n"; } int x; }; template<> class B { void fun(){ std::cout<<"2\n"; } int x; }; template<> class B { void fun2(){//完全不同 std::cout<<"2\n"; } int x; }; int main(){ B x;//调用特化版本 x.fun();//2 B x;//调用M原有模板版本 x.fun();//1 } ``` ==部分特化== ```c #include #include template<typename T, typename T2> class B { void fun(){ std::cout<<"1\n"; } int x; }; template class B<int, T2>//部分特化 { void fun2(){ std::cout<<"2\n"; } int x; }; int main(){ B<int,double> x;//调用部分特化版本 x.fun2();//2 } ``` ```c #include #include template class B { void fun(){ std::cout<<"1\n"; } int x; }; template class B<T*>//部分特化 { void fun2(){ std::cout<<"2\n"; } int x; }; int main(){ B<int*> x;//调用部分特化版本 x.fun2();//2 } ``` ## 2.6.类模板的实参推导(从 C++17 开始) - 基于构造函数的实参推导 ```c #include #include template class B { B(T input){} void fun(){ std::cout<<"1\n"; } int x; }; int main(){ B x(3);//基于构造函数的实参推导:构造时为int,T==int } ``` - [用户自定义的推导指引](https://www.shenlanxueyuan.com/course/329/task/11704/show) std::pair x{3,3,14};//std::pair<int,double> - 注意:引入实参推导并不意味着降低了类型限制! - C++ 17 之前的解决方案:引入辅助模板函数 # 三.[Concepts(概念)](https://zh.cppreference.com/w/cpp/language/constraints) ## 3.1.模板的问题:没有对模板参数引入相应的限制 - 参数是否可以正常工作,通常需要阅读代码进行理解 - 编译报错友好性较差 (vector<int&>) ```c #include #include template void fun(){ //...其实并不知道T的限制,不知道T支持哪些操作 } int main(){ } ``` ## 3.2.( C++20 ) Concepts :编译期谓词,基于给定的输入,返回 true 或 false - 与 constraints ( require 从句)一起使用限制模板参数 - 通常置于表示模板形参的尖括号后面进行限制 ```c #include #include template concept IsAvail = std::is_same_v<T,int> || std::is_same_v<T,float>; int main(){ std::cout<<IsAvail<<std::endl;//1 std::cout<<IsAvail<<std::endl;//0 } ``` ```c #include #include template concept IsAvail = std::is_same_v<T,int> || std::is_same_v<T,float>; template requires IsAvail// requires 从句 void fun(T input){ } int main(){ fun(3);//合法 fun(true);//非法:true不满足IsAvail } ``` ## 3.3.Concept 的定义与使用 - 包含一个模板参数的 Concept - 使用 requires 从句 - 直接替换 typename ```c #include #include template concept IsIntOrFloat = std::is_same_v<T,int> || std::is_same_v<T,float>; template//直接替换 typename void fun(T input){ } int main(){ fun(3);//合法 fun(true);//非法:true不满足IsIntOrFloat } ``` - 包含多个模板参数的 Concept - 用做类型 constraint 时,少传递一个参数,推导出的类型将作为首个参数 ```c #include #include template concept IsAvail = std::is_same_v<T,T2> ; template void fun(T input){ } int main(){ std::cout<<IsAvail<int,char><<std::endl;//0 } ``` ```c #include #include template<typename T, typename T2> concept IsAvail = !std::is_same_v<T,T2> ; template<typename T, typename T2> requires IsAvail<T, T2>// requires 从句 void fun(T input1, T2 input2){ } int main(){ fun(3,1.4);//合法 fun(3,4);//非法 } ``` ```c #include #include template<typename T, typename T2> concept IsAvail = !std::is_same_v<T,T2> ; template<IsAvail T> requires IsAvail<T, int>// requires 从句 void fun(T input1){ } //等价于 template void fun(T input1){ } int main(){ fun(1,3);//合法 } ``` ## 3.4.[requires 表达式](https://zh.cppreference.com/w/cpp/language/constraints) 此部分参考:cppreference讲解 - 简单表达式(简单要求):表明可以接收的操作 - 类型表达式(类型要求):表明是一个有效的类型 - 复合表达式(复合要求):表明操作的有效性,以及操作返回类型的特性 - 嵌套表达式(嵌套要求):包含其它的限定表达式 ## 3.5.注意区分 requires 从句与 requires 表达式 ## 3.6.requires 从句会影响重载解析与特化版本的选取 - 只有 requires 从句有效而且返回为 true 时相应的模板才会被考虑 - requires 从句所引入的限定具有偏序特性,系统会选择限制最严格的版本 ## 3.7.特化小技巧:在声明中引入 “ A||B” 进行限制,之后分别针对 A 与 B 引入特化 # 四.模板相关内容 ## 4.1.模板相关内容——数值模板参数与模板模板参数 ### 4.1.1.模板可以接收(编译期常量)数值作为模板参数 - template class Str; ```c #include #include template//使用数值作为模板参数 int fun(int x){ return x + a; } int main(){ fun<3>(5);//8 } ``` - template <typename T, T value> class Str; ```c #include #include template <typename T, T a> int fun(int x){ return x + a; } int main(){ fun<int, 3>(5);//8 } ``` - (C++ 17) template class Str; ```c #include #include template int fun(){ } int main(){ fun<3>(); } ``` - (C++ 20) 接收字面值类对象与浮点数作为模板参数 - 目前 clang 12 不支持接收浮点数作为模板参数 - C++ 20之前,不支持浮点数作为模板参数 ```c #include #include template int fun(){ } int main(){ fun<3.14f>(); constexpr float x = 10000 - 10000 + 3.14; fun(); //内部计算会有一些误差 constexpr float x2 = 10000 + 3.14 - 10000; fun(); //三种方式计算出来的结果不尽相同,因为内部计算实际上会有一定的误差 } ``` ### 4.1.2.接收模板作为模板参数 - template <template class C> class Str; ```c #include #include template class C{}; template<template class T2> void fun(){ T2 tmp; } int main(){ fun(); } ``` - (C++17) template <template typename C> class Str; ```c #include #include template class C{}; template<template typename T2> void fun(){ T2 tmp; } int main(){ fun(); } ``` - C++17 开始,模板的模板实参考虑缺省模板实参( clang 12 支持程度有限) - Str 是否支持? ## 4.2.模板相关内容——别名模板与变长模板 ### 4.2.1.[可以使用 using 引入别名模板](https://zh.cppreference.com/w/cpp/language/type_alias) ```c #include template using MyType = T; template using AddPinter = T*; int main(){ MyType x; AddPointer y;//y-->int* } ``` - 为模板本身引入别名 ```c template struct Alloc { }; template using Vec = vector<T, Alloc>; // 类型标识为 vector<T, Alloc> Vec v; // Vec 等同于 vector<int, Alloc> ``` - 为类模板的成员引入别名 ```c #include template struct B{ using TP = T*; } template using MyPointer = typename B::TP; int main(){ MyPointer y;//y-->int* } ``` - 别名模板不支持特化,但可以基于类模板的特化引入别名,以实现类似特化的功能 ```c #include template struct B{ using TP = T*; } template using MyPointer = T*; template <>//特化 using MyPointer = T*;//报错:别名模板不支持特化 int main(){ MyPointer y;//y-->int* } ``` 解决:基于类模板的特化引入别名,以实现类似特化的功能 ```c #include template struct B{ using type = T*; } template <>//特化 struct B{ using type = int&; } template using MyPointer = typename B::type; int main(){ MyPointer y;//y-->int& } ``` - 注意与实参推导的关系 ```c #include template using M = T*; template void fun(M input){//等价于: fun(T* input) } int main(){ int x; fun(&x); } ``` ```c #include template struct B{ using type = T*; }; template using M = typename B::type;//报错:没有办法触发实参推导 int main(){ int x; fun(&x); } ``` ### 4.2.2.变长模板( Variadic Template ) ```c #include template <typename T, typename T2>//fun固定只能接受两个参数 void fun(){ } int main(){ } ``` - [变长模板参数与参数包](https://zh.cppreference.com/w/cpp/language/parameter_pack) - 变长模板参数可以是数值、类型或模板/还有一个函数参数包 ```c #include template //fun可以接受0到多个int形的参数:接收的是数值 void fun(){ } int main(){ fun<1,2,3>(); } ``` ```c #include template //fun可以接受0到多个不同类型的参数 void fun(){ } int main(){ fun<int,double,char>(); } ``` ```c #include template <typename class... a>//fun可以接受0到多个模板参数,同时每个模板参数也是模板 void fun(){ } int main(){ } ``` ```c #include template void fun(T... args){ } int main(){ fun<int,double,char>(3,5.3,'c'); fun(3,5.3,'c');//可以自动推导 } ``` 更多讲解,参考cpp reference - sizeof... 操作:在变长模板函数中获取参数的个数 ```c #include template void fun(T... args){ std::cout<<sizeof...(T)<<std::endl; } int main(){ fun(3,5.3,'c');//3 } ``` - 注意变长模板参数的位置 ```c #include template class C; template <typename T1, typename T2> class B; template <typename... T, typename T2> class B<C, T2> { }; void fun(T... args){ std::cout<<sizeof...(T)<<std::endl; } int main(){ fun(3,5.3,'c');//3 } ``` ## 4.3.[模板相关内容——包展开与折叠表达式](https://zh.cppreference.com/w/cpp/language/parameter_pack) 更详细的解释参考 cpp reference ### 4.3.1.(C++11) 通过包展开技术操作变长模板参数 - 包展开语句可以很复杂,需要明确是哪一部分展开,在哪里展开 ```c #include template //T... -> int,double,char void fun(T... args){ } int main(){ fun<int, double,char>(3,5.3,'c'); } ``` ### 4.3.2.[(C++17) 折叠表达式 (cpp reference)](https://zh.cppreference.com/w/cpp/language/fold) - 基于逗号的折叠表达式应用 - 折叠表达式用于表达式求值,无法处理输入(输出)是类型与模板的情形 ```c #include void fun(){} template <typename U, typename... T> void fun(U u, T... args){ std::cout<< u<<std::endl; fun(args...);//递归调用,进行展开打印 } int main(){ fun(1,2,"hello",'c'); } ``` ==折叠表达式:== ```c #include template void fun(T... args){ ((std::cout<<args<<std::endl), ...); } int main(){ fun(1,2,"hello",'c');//递归调用,进行展开打印 } ``` ==具体是如何展开的,参考 cpp reference== ```c #include template<typename ...Args> int sum(Args&&... args) { return (args + ... + ); // OK } template<typename ...Args> int sum(Args&&... args) { return (... + args); // OK } int main(){ std::cout<< sum(1,2,3,4,5)<<std::endl;//15 } ``` ## 4.4.模板相关内容——完美转发与 lambda 表达式模板 ### 4.4.1.[(C++11) 完美转发: std::forward 函数](https://zh.cppreference.com/w/cpp/utility/forward) - 通常与万能引用结合使用 - 同时处理传入参数是左值或右值的情形 ```c #include void g(int&){ std::cout<<"l-reference\n"; } void g(int&&){ std::cout<<"r-reference\n"; } int main(){ int x = 3; g(x);//l-reference g(4);//r-reference } ``` ```c #include void g(int&){ std::cout<<"l-reference\n"; } void g(int&&){ std::cout<<"r-reference\n"; } template vois fun(T input){ vois fun(T&& input){//万能引用,相同的结果 std::cout<<"Hello\n"; g(input); } int main(){ int x = 3; fun(x);//Hello\nl-reference fun(4);//Hello\nl-reference - T input 变为了左值,这并不是我们想要的结果,我们想要的是r-reference } ``` 解决:完美转发 ```c #include #include void g(int&){ std::cout<<"l-reference\n"; } void g(int&&){ std::cout<<"r-reference\n"; } template vois fun(T&& input){//万能引用,相同的结果 std::cout<<"Hello\n"; g(std::forward(input)); } int main(){ int x = 3; fun(x);//Hello\nl-reference fun(4);//Hello\nr-reference } ``` ```c #include #include void g(int&){ std::cout<<"l-reference\n"; } void g(int&&){ std::cout<<"r-reference\n"; } template<typename T, typename T2> vois fun(T&& input. T2&& input2){ std::cout<<"Hello\n"; g(std::forward(input), std::forward(input2)); } int main(){ int x = 3; fun(x);//Hello\nl-reference fun(4);//Hello\nr-reference } ``` 包展开: ```c #include #include void g(int&){ std::cout<<"l-reference\n"; } void g(int&&){ std::cout<<"r-reference\n"; } template vois fun(T&&... args){ std::cout<<"Hello\n"; g(std::forward(args)...); } int main(){ int x = 3; fun(x);//Hello\nl-reference fun(4);//Hello\nr-reference } ``` ### 4.4.2.(C++20) [lambda表达式模板](https://zh.cppreference.com/w/cpp/language/lambda) 参考cpp reference ## 4.5.模板相关内容——消除歧义与变量模板 ### 4.5.1.使用 typename 与 template 消除歧义 - 使用 typename 表示一个依赖名称是类型而非静态数据成员 ```c #include #include template void fun(){ T::internal* p;//* 可以是乘,可以是解引用 } int main(){ } ``` ```c #include #include struct Str{ inline const static internal = 3; }; int p = 5; template void fun(){ //internal 依赖于 T T::internal* p;//* 是乘 } int main(){ fun();//3*5 } ``` ```c #include #include struct Str{ using internal = int; }; int p = 5; template void fun(){ //使用 typename 消除歧义 typename T::internal* p;//int * } int main(){ fun(); } ``` - 使用 template 表示一个依赖名称是模板 ```c #include #include struct Str{ template static void internal(){ } }; struct Str2{ inline const static int internal = 100; } template void fun(){ T::internal();//err:有歧义:video-94-3-9.00 T::template internal();//template 消除歧义 } int main(){ fun(); } ``` - template 与成员函数模板调用 ```c #include #include struct Str{ template static void internal(){ } }; template void fun(){ T obj; obj.internal();//err:internal 依赖于T obj.template::internal();//template消除歧义 } int main(){ fun(); } ``` ### 4.5.2.(C++14) ==变量模板== - template T pi = (T)3.1415926; - 其它形式的变量模板 ```c #include #include template T pi = (T)3.1415926; int main(){ std::cout<< pi <<std::endl;//3.1415926 std::cout<< pi <<std::endl;//3 } ``` ```c #include #include template unsigned MySize = sizeof(T); int main(){ std::cout<< MySize <<std::endl;//4 std::cout<< MySize <<std::endl;//4 } ``` ```c #include #include template <typename T, unsigned v> unsigned MySize = (sizeof(T) == v); int main(){ std::cout<< MySize<float, 4> <<std::endl;//1 std::cout<< MySize<int, 2> <<std::endl;//0 } ``` [is_same_v](https://zh.cppreference.com/w/cpp/types/is_same)就是一个变量模板 ```c template< class T, class U > inline constexpr bool is_same_v = is_same<T, U>::value; ```

CPP-Note © 2024 | C++ 学习笔记

This site uses Just the Docs, a documentation theme for Jekyll.