IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> C++模板学习笔记——模板实参 -> 正文阅读

[JavaScript知识库]C++模板学习笔记——模板实参

对于函数模板,编译器通过隐式推断模板实参。其中,从函数实参来确定模板实参的过程被称为模板实参推断。在模板实参推断过程中,编译器使用函数调用中的实参类型来寻找模板实参,用这些模板实参生成的函数版本与给定的函数调用最为匹配;

类型转换与模板类型参数:

对于模板函数和普通函数而言,在函数调用过程中实参将会用来初始化函数的形参;

在模板函数中,采用特殊的初始化规则(编译器通常不会对实参进行类型转换,而是生成一个新的模板实参)。

  1. const转换:
    可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参
  2. 数组或函数指针转换:
    如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转化。
    其他类型转换(算术转换、派生类向基类的转换、用户自定义转换)都不能应用于函数模板。
template<typename T> T fobj(T,T);
template<typename T> T fref(const T&,const T&);
string s1("a value");
const string const_s2("another value");
fobj(s1,const_s2);   //调用fobj(string,string);const被忽略
fref(s1,const_s2);  //调用fref(const string&,const string&)
int a[10],b[43];
fobj(a,b);  //调用f(int*,int*)
fref(a,b); //错误:数组类型不匹配(函数形参是引用类型)

将实参传递给带模板类型的函数型参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。

  1. 使用相同模板参数类型的函数形参:由于模板函数只允许有限的几种类型转换,因此传递给这些形参的实参必须具有相同的类型。
  2. 正常类型转换应用于普通函数实参:在模板函数形参中,未涉及模板类型参数的类型不进行特殊处理,他们正常转换为对应形参的类型;
template<typename T> ostream &print(ostream& os,const T& obj){
	return os >> obj;
}

print(cout,32);         //实例化print(ostream&,int);
ofstream f("output");
print(f,10);          //实例化print(ostream&,int);  将f转换为了ostream&

如果函数参数类型不是模板参数,则对实参进行正常的类型转换

函数模板显式实参

在某些情况下编译器无法判断出模板实参的类型。其他情况下,我们允许用户控制模板实例化。
  1. 指定显式模板实参
    作为一个允许用户指定使用类型的例子,我们将定义一个名为sum的函数模板。它接受两个不同类型的参数。我们希望允许用户指定结果的类型,这样,用户就可以选择合适的精度。
template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);

//T1是显式指定的,T2和T3式从函数实参类型推断得来的
auto val3 = sum<long long>(i,lng);    //long long sum(int,long);

显式模板实参按从左至右的顺序与对应的模板参数匹配; 看下面的代码:

template<typename T1,typename T2,typename T3>
T3 sum (T1,T2);
//对于此种情况,我们必须为所有三个形参指定实参
auto val3 = sum<long long,int,long>(i,lng);
  1. 正常类型转换应用于显式指定的实参:
    对于模板类型参数已经指定了的函数实参,可以进行正常的类型转换

尾置返回类型于类型转换

 但我们希望用户确定返回类型时,用显式模板实参表示模板函数的返回类型是很有效大的。
 但在其他情况下,要求显示指定模板实参会给用户增添额外负担,而且不会带来好处。

例如,我们希望编写一个函数,接受表示序列的一对迭代器和返回类型中一个元素的引用:

template<typename It>
auto fcn(It beg,It end) -> decltype(*beg){    //decltype可以获取变量的类型
	//处理序列
	return *beg;  //返回序列中一个元素的引用
}

std::string s1("abcdefg");
auto ret = fcn(s1.begin(),s1.end());   //此时ret的返回类型为char&
  1. 进行类型转换的标准库模板类
    有时我们无法直接获取所需要的类型。例如,我们可以希望编写一个类型fcn的函数,但返回一个元素的值而非引用。
template<typename It>
auto fcn2(It beg,It end) ->
		typename remove_reference<decltype(*begin)>::type{
	//处理序列
	return *beg;   //返回序列中一个元素的拷贝
}

函数指针与实参推断

当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参

例如,假定我们有一个函数指针,它指向的函数返回int,接受两个参数,每个参数都是指向const int的引用。我们可以使用该指针指向compare的一个实例

template<typename T> int compare(const T&,const T&);
//pf1指向实例int compare(const int&,const int&);
int (*pf1)(const int&,const int&) = compare;

当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板 参数,能唯一确定其类型或值

模板实参推断和引用

template <typename T> void f(T &p);

函数参数p是一个模板类型参数T的引用,编译器会应用正常的引用绑定规则:const是底层的,不是顶层的

  1. 从左值引用函数参数推断类型
    当一个函数参数是模板类型参数的一个普通(左值)引用时(T&),绑定规则告诉我们,只能传递给它一个左值。实参可以是const类型,也可以不是。如果实参是const的,则T将被推断为const类型(const T&):
template<typename T> void fl(T&); 
fl(i);   // i 是一个int;模板参数类型T是int
fl(ci);   //ci是一个const int;模板参数T是const int
fl(5);    // 错误,传递给一个&参数的实参必须是一个左值

如果一个函数参数的类型是const T&,正常的绑定规则告诉我们可以传递给它任何类型的实参—— 一个对象(const or not const )、一个临时对象或是一个字面常量值。

template<typename T> void f2(const T&);
//f2 中的参数是const& ;实参中的const是无关的
//在每个调用中,f2的函数实参都被推断为const T&
f2(i);   // i 是一个int,模板参数T是int;
f2(ci);   //ci 是一个const int, 但模板参数T 是int
f2(5);    //一个const& 参数可以绑定到一个右值;T是int
  1. 从右值引用函数参数推断类型
template<typename T> void f3(T&&);

当一个函数参数是一个右值引用(T&&)时,正常绑定规则告诉我们可以传递给他一个右值。

  1. 引用折叠和右值引用参数

     通常我们不能将一个右值引用绑定到一个左值上。但是,c++语言在正常绑定规则之外定义了两个例外规则,允许这种绑定 
    
  • 第一个例外规则影响右值引用参数的推断如何进行。当我们将一个左值传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&),编译器推断模板类型参数为实参的左值引用类型。 例如,当调用f3(i)时,编译器推断T的类型为int&,而非int。
    可以将上述情况理解为定义了一个了类型为int& 的右值引用 ,通常情况下,我们不能(直接)定义一个引用的引用,但是,通过类型别名或通过模板类型参数间接定义是可以的。
  • 第二个例外绑定规则:如果我们间接创建一个引用的引用,则这些引用形成了"折叠"。在所有情况下(除了一个例外),引用会折叠成一个普通的左值引用类型。在新标准中,折叠规则扩展到了右值引用。只有在一种情况下引用会折叠成右值引用:右值引用的右值引用。X& &、X& && 和 X&& &都折叠为类型 X& ; 类型X&& && 折叠为X&&

引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数

void f3<int&>( int&);  //当T是int&时,函数折叠为int&

如果一个函数参数是指向模板类型参数的右值引用,则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用。

  1. 编写接受右值引用参数的模板函数
template<typename T> void f3(T&& val){
	T t = val;  //拷贝还是绑定一个引用?
	t = fcn(t);  //赋值只改变t还是既改变t又改变val?
	if(val == t){...} //若T是引用类型,则一直为true
}

在实际情况中,右值引用通常用于两种情况:模板转发其实参 或 模板被重载

  1. 转发

     某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。再此情况下,我们需要保持被转发实参的所有性质,包括实参是否是const的以及实参是左值还是右值。
    
//接受一个可调用对象和另外两个参数的模板
//对“翻转”的参数调用给定的可调用对象
//flip1是一个不完整的实现:顶层const和引用丢失
template <typename F,typename T1,typename T2>
void flip1(F f,T1&& t1,T2&& t2){
	f(t2,t1);
}

如果一个函数参数是指向模板类型参数的右值引用,他对应的实参的const属性和左值/右值属性将得到保持;

  • 在调用中使用std::forward保持类型信息

      forward返回该显式实参类型的右值引用。即,forward<T>的返回类型为T&&
    
template<typename F,typename T1,typename T2>
void flip(F f,T1 &&t1,T2 &&t2){
	f(std::forward<T2>(t2),std::forward<T1>(t1));
}

当用于一个指向模板参数类型的右值引用函数参数时,forward会保持实参类型的所有细节


参考文献:C++ Primer(第五版)
本文仅为本人学习笔记,仅做记录用途

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-16 19:33:27  更:2021-10-16 19:35:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 18:14:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码