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

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

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

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

  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(第五版)
本文仅为本人学习笔记,仅做记录用途

C++模板学习笔记——模板实参相关推荐

  1. 设计模式学习笔记——模板(Template)模式

    设计模式学习笔记--模板(Template)模式 @(设计模式)[设计模式, 模板模式, template, 模板方法] 设计模式学习笔记模板Template模式 基本介绍 模板案例 类图 实现代码 ...

  2. Mustache模板学习笔记

    Mustache模板学习笔记 1 初体验 1.1 还可以对象形式做数据源 1.2 {{#param}}这个标签很强大,有if判断.forEach的功能. 1.2.1 如果迭代的是数组,还可以用{{.} ...

  3. 图论01.最短路专题_学习笔记+模板

    图论01.最短路专题_学习笔记+模板 一.定义与性质 ● 需要的前导知识点 路径 最短路 有向图中的最短路.无向图中的最短路 单源最短路.每对结点之间的最短路 ● 最短路的性质 对于边权为正的图,任意 ...

  4. C++学习笔记:模板

    C++学习笔记:模板 1.函数模板 2.类模板 2.1类模板注意事项 2.2类模板中函数的创建时机 2.3类模板对象作函数参数时 2.4类模板与继承 2.5类模板分文件编写 2.6类模板友元 2.6. ...

  5. image是否有disabled属性_Vue学习笔记 模板语法、计算属性

    点击上方"蓝字"关注我们吧! vue学习笔记 官网:https://cn.vuejs.org/v2/guide/ 1.vue体验 demo示例: image.png 示例代码: & ...

  6. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的"章节7:创建TPL自定义模板",做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. ...

  7. C++学习笔记-------模板(template)

    文章目录 1.函数模板 2.类模板 2.1类模板的基本使用 2.2类模板中的友元 3.模板默认参数 3.1模板函数的默认参数 3.2类模板默认参数 4.成员模板 4.1普通类的成员模板 4.2模板类的 ...

  8. C++ 函数模板和排序的函数模板——学习笔记

    我们在使用重载函数时,只是使用了函数名,而函数体还是得分别定义,在C++中函数模板为我们很好的解决了这个问题. 1.函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函 ...

  9. C++学习笔记—模板与STL

    C++提高编程 本阶段主要针对C++泛型编程和STL技术做详细讲解,探讨C++更深层的使用 1 模板 1.1 模板的概念 模板就是建立通用的模具,大大提高复用性 例如生活中的模板 一寸照片模板 PPT ...

最新文章

  1. TensorFlow平台搭建
  2. Android 个人学习笔记- 导入android项目,无法自动生成R文件的解决方法
  3. 电脑开机密码忘记了怎么办?
  4. Bigraph Extension
  5. UNIX(多线程):22---几种常见的线程池
  6. 【IDEA】Error:java: Compilation failed: internal java compiler error
  7. 蒲公英枸杞菊花可以一起泡茶喝吗?
  8. Combiner合并的使用案例
  9. Matlab深度学习——安装deep learning toolbox工具箱
  10. InnoDB行记录格式
  11. APFNet训练+测试复现过程记录
  12. 【华为云】 搭建TFP站点心得体会
  13. Matlab之绘制瀑布图
  14. 《流浪地球》海报丨见证小破球24亿票房逆袭之路
  15. 九宫格动态密码--快速入门
  16. 论文总结——因果发现与推断
  17. 3.0-LAB1-C1 实验
  18. 【YAML】【YAML的实践】【YAML的使用学习记录】
  19. dn什么意思_DN是啥意思
  20. linux中获取几天前或者几天后的日期

热门文章

  1. Charles 安装图解(Mac 抓包工具)
  2. 全球与中国零卡路里饮料市场深度研究分析报告
  3. 视频教程-企业级电商大数据推荐系统实战-大数据
  4. 小微权力监督平台功能列表:实现三资管理、大数据比对、监督举报等
  5. 如何使Maltab中用randi函数生成的随机数变成固定数
  6. 计算机专业同济大学和华科,学计算机专业是去华中科技大学还是同济大学好?学霸教你选择...
  7. 【翻译】粉碎Gadgets:使用就地代码随机化防御面向返回的编程——Smashing the Gadgets: Hindering Return-Oriented Programming...
  8. apk v1+v2命令行签名命令
  9. 46家公司面试笔试题
  10. YzmCMS 5.2.X 文章发布模块及使用说明