c++11-17 模板核心知识(五)—— 理解模板参数推导规则
- Case 1 : ParamType是一个指针或者引用,但不是universal reference
- Case 2 : ParamType是Universal Reference
- Case 3 : ParamType既不是指针也不是引用
- 数组作为参数
- 函数作为参数
首先我们定义一下本文通用的模板定义与调用:
template<typename T>
void f(ParamType param);
......
f(expr); // call f with some expression
在编译阶段使用expr
来推断ParamType
和T
这两个类型。这两个类型通常不同,因为ParamType
会有const
和引用等修饰。例如:
template<typename T>
void f(const T& param); // ParamType is const T&
int x = 0;
f(x); // call f with an int
这里,T被推断成int
,但是ParamType
的类型是const T&
。
直觉下T
的类型应该和expr
的一样,比如上面的例子中,expr
和T
的类型都是int
。但是会有一些例外情况:T
的类型不仅依赖expr
,还依赖ParamType
。总共分为三大类:
ParamType
是一个指针或者引用,但不是universal reference
(或者叫forwarding references
).ParamType
是一个universal reference
。ParamType
既不是指针也不是引用。
Case 1 : ParamType是一个指针或者引用,但不是universal reference
- 如果
expr
是一个引用,忽略其引用部分。 - 比较
expr
与ParamType
的类型来决定T
的类型。
T&
template<typename T>
void f(T& param); // param is a reference
......
int x = 27; // x is an int
const int cx = x; // cx is a const int
const int& rx = x; // rx is a reference to x as a const int
// call f
f(x); // T is int, param's type is int&
f(cx); // T is const int, param's type is const int&
f(rx); // T is const int, param's type is const int&
上面例子是左值引用,但是这点对右值引用也适用。
注意第三点,const
修饰符依旧保留。 这和普通函数的类似调用有区别:
void f(int &x){
}
...
const int x = 10;
f(x); // error
const T&
如果给ParamType
加上const
,情况也没有太大变化:
template<typename T>
void f(const T& param); // param is now a ref-to-const
......
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
......
f(x); // T is int, param's type is const int&
f(cx); // T is int, param's type is const int&
f(rx); // T is int, param's type is const int&
T*
改为指针也一样:
template<typename T>
void f(T* param); // param is now a pointer
......
int x = 27;
const int *px = &x;
f(&x); // T is int, param's type is int*
f(px); // T is const int, param's type is const int*
Case 2 : ParamType是Universal Reference
- 如果
expr
是左值,那么T
和ParamType
会被推断为左值引用。 - 如果
expr
是右值,那么就是Case 1的情况。
template<typename T>
void f(T&& param); // param is now a universal reference
......
int x = 27;
const int cx = x;
const int& rx = x;
调用:
f(x); // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(rx); // rx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is rvalue, so T is int, param's type is therefore int&&
如果之前了解过完美转发和折叠引用的概念,结合Case1,这一个规则还是比较好理解的。
注意区别Universal Reference与右值引用
这两点需要区分清楚,比如:
template<typename T>
void f(T&& param); // universal reference
template<typename T>
void f(std::vector<T>&& param); // rvalue reference
有一个通用规则 : universal reference
会有类型推断的过程。具体在后面的单独文章会讲,跟这篇文章的主题关系不大,这里稍微提一下 : )
Case 3 : ParamType既不是指针也不是引用
这种情况就是pass-by-value的情况:
template<typename T>
void f(T param); // param is now passed by value
这意味着,param是一个被拷贝的全新对象,也就是param决定着T的类型:
- 如果
expr
是引用类型,忽略。 - 如果
expr
带有const、volatile,忽略。
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int
忽略const和volatile也比较好理解:参数是值拷贝,所以形参和实参其实是互相独立的。正如下面代码可以将const int
传递给int
,但是声明为引用则不行:
void f(int x){
}
int main() {
const int x = 10;
f(x);
}
注意忽略的const是针对参数本身的,而不针对指针指向的const对象:
template<typename T>
void f(T param);
......
const char* const ptr = "Fun with pointers"; // ptr is const pointer to const object
f(ptr); // pass arg of type const char * const
这个按照值传递的是ptr,所以ptr的const会被忽略,但是ptr指向的对象依然是const。
数组作为参数
数组类型和指针类型是两种类型,但是有时候他们是可以互换的,比如在下面这种情况下,数组会decay成指针:
const char name[] = "J. P. Briggs"; // name's type is const char[13]
const char * ptrToName = name; // array decays to pointer
在普通函数中,函数形参为数组类型和指针类型是等价的:
void myFunc(int param[]);
void myFunc1(int* param); // same function as above
但是数组作为模板参数是比较特殊的一种情况。
ParamType按值传递
template<typename T>
void f(T param); // template with by-value parameter
......
const char name[] = "J. P. Briggs"; // name's type is const char[13]
f(name); // name is array, but T deduced as const char*
这种情况下,T
被推断为指针类型const char*
.
ParamType为引用类型
template<typename T>
void f(T& param);
......
const char name[] = "J. P. Briggs"; // name's type is const char[13]
f(name); // pass array to f
现在T
被推断为数组类型const char [13]
,ParamType
为const char (&)[13]
,这种情况是很特殊的,要与ParamType
按值传递区分开。
我们可以利用上面这种特性定义一个模板来推断数组的大小,这种用法还蛮常见的:
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept {
return N;
}
......
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 };
std::array<int, arraySize(keyVals)> mappedVals;
函数作为参数
上面讨论的关于数组的情况同样适用于函数作为参数,函数类型同样也可以decay
成函数指针:
void someFunc(int, double); // someFunc is a function;type is void(int, double)
template <typename T> void f1(T param); // in f1, param passed by value
template <typename T> void f2(T ¶m); // in f2, param passed by ref
f1(someFunc); // param deduced as ptr-to-func; type is void (*)(int, double)
f2(someFunc); // param deduced as ref-to-func; type is void (&)(int, double)
不过这在平时应用中也没有太大差别。
(完)
朋友们可以关注下我的公众号,获得最及时的更新:
c++11-17 模板核心知识(五)—— 理解模板参数推导规则的更多相关文章
- c++11-17 模板核心知识(十五)—— 解析模板之依赖型类型名称与typename Dependent Names of Types
模板名称的问题及解决 typename规则 C++20 typename 上篇文章c++11-17 模板核心知识(十四)-- 解析模板之依赖型模板名称 Dependent Names of Templ ...
- c++11-17 模板核心知识(九)—— 理解decltype与decltype(auto)
decltype介绍 为什么需要decltype decltype(auto) 注意(entity) 与模板参数推导和auto推导一样,decltype的结果大多数情况下是正常的,但是也有少部分情况是 ...
- c++11-17 模板核心知识(十一)—— 编写泛型库需要的基本技术
Callables 函数对象 Function Objects 处理成员函数及额外的参数 std::invoke<>() 统一包装 泛型库的其他基本技术 Type Traits std:: ...
- c++11-17 模板核心知识(二)—— 类模板
类模板声明.实现与使用 Class Instantiation 使用类模板的部分成员函数 Concept 友元 方式一 方式二 类模板的全特化 类模板的偏特化 多模板参数的偏特化 默认模板参数 Typ ...
- c++11-17 模板核心知识(十二)—— 模板的模板参数 Template Template Parameters
概念 举例 模板的模板参数的参数匹配 Template Template Argument Matching 解决办法一 解决办法二 概念 一个模板的参数是模板类型. 举例 在c++11-17 模板核 ...
- c++11-17 模板核心知识(十四)—— 解析模板之依赖型模板名称(.template/->template/::template)
tokenization与parsing 解析模板之类型的依赖名称 Dependent Names of Templates Example One Example Two Example Three ...
- c++11-17 模板核心知识(一)—— 函数模板
1.1 定义函数模板 1.2 使用函数模板 1.3 两阶段翻译 Two-Phase Translation 1.3.1 模板的编译和链接问题 1.4 多模板参数 1.4.1 引入额外模板参数作为返回值 ...
- c++11-17 模板核心知识(十)—— 区分万能引用(universal references)和右值引用
引子 如何区分 模板参数 const disqualify universal reference auto声明 引子 T&&在代码里并不总是右值引用: void f(Widget&a ...
- c++11-17 模板核心知识(十三)—— 名称查找与ADL
名称分类 名称查找 ordinary lookup ADL (Argument-Dependent Lookup) 官网的例子 ADL的缺点 在C++中,如果编译器遇到一个名称,它会寻找这个名称代表什 ...
随机推荐
- selenium---输入内容后搜索
from time import sleep from selenium import webdriver br = webdriver.Chrome() url = "https://ww ...
- Activiti的流程实例【ProcessInstance】与执行实例【Execution】
最近,我在做流程引擎Activiti相关的东西,刚开始时的一个知识点困扰了我许久,那就是Activiti的ProcessInstance与Execution的区别,这是一个Activiti的难点,能够 ...
- Kubernetes 使用 ceph-csi 消费 RBD 作为持久化存储
原文链接:https://fuckcloudnative.io/posts/kubernetes-storage-using-ceph-rbd/ 本文详细介绍了如何在 Kubernetes 集群中部署 ...
- 对于某东平台XX娃娃的用户体验进行(严肃、限速)数据分析
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 本次的爬取目标是某东的一个商品,但从来没有用过,所以本人很好奇.我们就采集这 ...
- Linux用户和组管理命令-用户属性修改usermod
用户属性修改 usermod 命令可以修改用户属性 格式: usermod [OPTION] login 常见选项: -u UID: 新UID -g GID: 新主组 -G GROUP1[,GROUP ...
- Unity实现代码控制音频播放
前言 很久没说过Unity了,现在说一下Unity用代码控制音频播放 准备工作 1.需要播放的音频 2.给需要加声音的对象加Audio Source组件 3.新建Play脚本,并绑定需要播放声音的对象 ...
- docker在win7下的使用
1,安装 win7下需要安装docker-toolbox,然后通过Docker Quickstart Terminal运行 2,加速 直接pull的话是拉取的docker hub上的镜像,速度非常慢, ...
- No compatible servers were found,You'll need to cancel this wizard and install one!
原文链接:https://www.jianshu.com/p/a11f93fb16ce 问题原因 笔记本重装的windows系统,重新安装mysql的时候,显示错误,看了一下缺失服务,实际上可能是缺少 ...
- SP22343 Norma--序列分治
Norma 传送门 题意简化: 定义一个区间的贡献为 \(max*min*len\),求给定序列中所有子区间的总贡献和 题解 考虑 \(O(n*log_2n)\) 的复杂度的做法 数据结构??? yz ...
- Luogu P4271 [USACO18FEB]New Barns P
题意 给一个一开始没有点的图,有 \(q\) 次操作,每次为加点连边或者查询一个点到连通块内所有点的距离最大值. \(\texttt{Data Range}:1\leq q\leq 10^5\) 题解 ...