《Effective Modern C++》Item 1: Understand template type deduction

本文详细介绍C++中模板类型推导的几种情况,包括非通用引用、通用引用及非引用类型推导规则,并通过实例解析如何根据参数类型正确推导模板参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引子

老爷子上来第一大章(Item 1-4)就讨论了类型推导,依次介绍了:

  • 模版类型推导
  • auto类型推导
  • decltype关键字
  • 获取变量类型的正确姿势

当然了,从最简单的auto关键字入手,确实是方便老c++用户熟悉c++11/14的快速方法。

本文就先介绍模板类型推倒吧。

正文

先看一段代码

template<typename T> 
void f(ParamType param);  

这是一个最简单的c++模板代码,根据ParamType的不同,我们可以将其分为如下几类

  • non-universal 引用 T&
  • universal引用 T&&
  • 非引用 : TT* 此时为传值,包括传指针(值)

1. non-universal引用 T&

这时根据传入的类型,我们推导规则如下:

  1. 如果传入的类型是引用类型,那么会忽视掉引用部分,如果是指针类型,则会忽视指针部分
  2. 如果传入的类型不是引用类型那么原来是什么类型就推导成什么类型

此时上面的代码变成

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

那么对于下面3个变量声明

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

当它们调用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&

显然这些类型推导是自然且符合我们预期的。

2. universal引用 T&&

虽然Item 1中尚未准确定义universal引用,但是为了方便读者,我们的提前剧透一下Item 24中的定义:

形如T&&的类型有两种情况:一种是普通的rvalue reference,另一种则被称为 universal reference,它既可能表示lvalue reference,也可能表示rvalue reference。区分他们的方式为: 如果一个函数模板参数的类型为T&&,其中T是需要推导的类型,那么此处的T&&指代universal reference。若不严格满足T&&类型或没有发生类型推导,则T&&指代rvalue reference。

详细的内容留在Item 24里面解释,这里不再赘述(等不及的读者也可以参考这篇文章)。

在这种情况下,我们适用如下规则:

  1. 如果传入的参数是lvalue,那么就推导成引用类型
  2. 如果是rvalue,则用上面non-universal的规则

下面回到最开始的代码,我们有

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&&

3. 非引用 TT*

此时就是最基础的传值(pass-by-value),注意到传指针本质上也是在传指针的值,所以我们将指针也归到这一类。此时我们适用如下规则:

  1. 如果传入参数是引用,忽略引用属性
  2. 如果传入参数不是引用类型,忽略constvolatile修饰符
    则我们的例子变成
template<typename T> 
void f(T param);  // param is now passed by value

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

之所以去掉cv修饰符,本质上是由于传值是对原来的对象进行了一次copy,自然constvolatile这种属性就丢失了。

为了加深理解,我们考虑一个更复杂的例子:

template<typename T>
void f(T param);            // param is still passed by value

const char* const ptr =     // ptr is const pointer to const object
     "Fun with pointers";

f(ptr);                     // so what is the type of T?

答案是const char*,这是由于我们是传值。故ptr会被拷贝一遍,这样失去了它本身的不变性(即星号右边的 const),但是它本身修饰的字符串的不变性仍然被保留。

最后我们在考虑两个特殊情况,即传入参数是数组和函数指针的情况。

4.传入参数为数组或者函数指针

由于历史原因,C++中数组类型会退化成指针类型。于是我们有如下的例子:

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]
const char * ptrToName = name;          // array decays to pointer

f(name);                                // name is array, but T deduced as const char*

因此在参数是传值时,数组会被推导成指针。

但是当参数是传引用时,就发生了变化:

template<typename T>
void f(T& param);   // template with by-reference parameter

f(name);            // pass array to f, so T is const char [13] and param's type is const char(&)[13]

除了数组,函数指针也会有类似的表现:

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)

总结

最后我们贴一下S.M.老爷子在本条目末尾的tips:

1.在模板类型推导中,如果传的参数是引用类型,则被当作非引用类型,即引用本身会被忽略;
2.当推导类型为universal引用时,左值参数会被特殊对待(被推导成引用);
3.当参数是传值时,cv修饰符会被忽略掉。
4.数组和函数作为参数时,会退化成指针,除非它本身作为引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值