首先我们定义一下本文通用的模板定义与调用:

template<typename T>
void f(ParamType param); ......
f(expr); // call f with some expression

在编译阶段使用expr来推断ParamTypeT这两个类型。这两个类型通常不同,因为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的一样,比如上面的例子中,exprT的类型都是int。但是会有一些例外情况:T的类型不仅依赖expr,还依赖ParamType。总共分为三大类:

  • ParamType是一个指针或者引用,但不是universal reference(或者叫forwarding references).
  • ParamType是一个universal reference
  • ParamType既不是指针也不是引用。

Case 1 : ParamType是一个指针或者引用,但不是universal reference

  • 如果expr是一个引用,忽略其引用部分。
  • 比较exprParamType的类型来决定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是左值,那么TParamType会被推断为左值引用。
  • 如果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]ParamTypeconst 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 &param); // 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 模板核心知识(五)—— 理解模板参数推导规则的更多相关文章

  1. c++11-17 模板核心知识(十五)—— 解析模板之依赖型类型名称与typename Dependent Names of Types

    模板名称的问题及解决 typename规则 C++20 typename 上篇文章c++11-17 模板核心知识(十四)-- 解析模板之依赖型模板名称 Dependent Names of Templ ...

  2. c++11-17 模板核心知识(九)—— 理解decltype与decltype(auto)

    decltype介绍 为什么需要decltype decltype(auto) 注意(entity) 与模板参数推导和auto推导一样,decltype的结果大多数情况下是正常的,但是也有少部分情况是 ...

  3. c++11-17 模板核心知识(十一)—— 编写泛型库需要的基本技术

    Callables 函数对象 Function Objects 处理成员函数及额外的参数 std::invoke<>() 统一包装 泛型库的其他基本技术 Type Traits std:: ...

  4. c++11-17 模板核心知识(二)—— 类模板

    类模板声明.实现与使用 Class Instantiation 使用类模板的部分成员函数 Concept 友元 方式一 方式二 类模板的全特化 类模板的偏特化 多模板参数的偏特化 默认模板参数 Typ ...

  5. c++11-17 模板核心知识(十二)—— 模板的模板参数 Template Template Parameters

    概念 举例 模板的模板参数的参数匹配 Template Template Argument Matching 解决办法一 解决办法二 概念 一个模板的参数是模板类型. 举例 在c++11-17 模板核 ...

  6. c++11-17 模板核心知识(十四)—— 解析模板之依赖型模板名称(.template/->template/::template)

    tokenization与parsing 解析模板之类型的依赖名称 Dependent Names of Templates Example One Example Two Example Three ...

  7. c++11-17 模板核心知识(一)—— 函数模板

    1.1 定义函数模板 1.2 使用函数模板 1.3 两阶段翻译 Two-Phase Translation 1.3.1 模板的编译和链接问题 1.4 多模板参数 1.4.1 引入额外模板参数作为返回值 ...

  8. c++11-17 模板核心知识(十)—— 区分万能引用(universal references)和右值引用

    引子 如何区分 模板参数 const disqualify universal reference auto声明 引子 T&&在代码里并不总是右值引用: void f(Widget&a ...

  9. c++11-17 模板核心知识(十三)—— 名称查找与ADL

    名称分类 名称查找 ordinary lookup ADL (Argument-Dependent Lookup) 官网的例子 ADL的缺点 在C++中,如果编译器遇到一个名称,它会寻找这个名称代表什 ...

随机推荐

  1. 【贪心算法】CF Emergency Evacuation

    题目大意 vjudge链接 给你一个车厢和一些人,这些人都坐在座位上,求这些人全部出去的时间最小值. 样例1输入 5 2 71 11 21 32 32 44 45 2 样例1输出 9 样例2输入 50 ...

  2. Linux关联文件扩展名和打开程序

    手动为Linux关联文件扩展名与打开程序. 当Linux不认识某种扩展名的时候,需要为此扩展名创建一个MIME类型.Linux是依据MIME类型而不是扩展名来决定打开程序的. 再将此种MIME类型与某 ...

  3. go 不停模拟 写日志

    package main import ( "os" "errors" "math/rand" "time" " ...

  4. spring boot:spring security实现oauth2+jwt管理认证授权及oauth2返回结果格式化(spring boot 2.3.3)

    一,为什么oauth2要整合jwt? 1,OAuth2的token技术有一个最大的问题是不携带用户信息,所以资源服务器不能进行本地验证, 以致每次对于资源的访问,资源服务器都需要向认证服务器的toke ...

  5. spring boot:使用poi导出excel电子表格文件(spring boot 2.3.1)

    一,什么是poi? 1,poi poi是用来兼容微软文档格式的java api, 它是apache的顶级项目之一, 也是我们在生产环境中导出excel时使用最多的库 2,poi官方网站: http:/ ...

  6. linux(centos8):安装java jdk 14 (java 14.0.2)

    一,下载jdk14 官方网站: https://www.oracle.com/java/ 下载页面: https://www.oracle.com/cn/java/technologies/javas ...

  7. Mac安装stf

    1.brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config 2.node版本8.x我的是8.15.0 3.npm i ...

  8. git折腾日志

    git for windows https://git-for-windows.github.io/ 基本配置 进入git bash后执行 $ git config --global user.nam ...

  9. 如何使用FastCGI处理自定义HTTP头

    对于如何使用FastCGI处理自定义HTTP头这里记录一下注意事项: 在FastCGI中,自定义头可以从环境变量获得: 获取时名字前面要加HTTP_,字母要全部大写: 发送头不能有下划线_,否则该字段 ...

  10. Oracl Linux KVM虚拟机备份

    Oracle Linux  KVM 作为Oracle Linux的一部分,基于KVM的Oracle Linux 服务器虚拟化解决方案在功能上得到了增强.用户可以利用Oracle Linux旧版本,将操 ...