Effective Modern C++ Item1 模板类型推导详解

函数模板举例

一般的函数模板的声明如下所示:

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

其调用的方法为:

f(expr);     // call f with some expression

在编译期间, 编译器会使用expr推导出两个类型: T 和 ParamType.通常来讲,这两个类型是不同的,因为ParamType往往带有其它的修饰符, 举例如下:

template<typename T>
void f(const T& param);  // ParamType is const T&

这里的ParamType类型是 const T&.

假如我们以一个int变量去调用这个函数 :

int x = 0;
f(x);  // call f with an int

T会被推导为int, 而ParamType 会被推导为const int&.


三种模板类型推导

模板类型推导可以分为以下三类:

  • 形参(上面的ParamType)是一个指针或引用, 但不是所谓的通用引用(即Scott Meyers书中的universal reference, 在这里可暂且理解为右值引用的模板类型: ParamType&&).
  • 形参是通用引用(universal reference, 即ParamType&&).
  • 形参即不是指针,也不是引用.

Case1: 形参声明为指针或引用,但不是通用引用

这是最简单的一种情况, ParamType是一个指针或引用, 但不是ParamType&& 类型的引用.在这种情况下,T的类型推导的规则为:

  1. 如果expr的类型是引用, 则忽略引用的成分.
  2. 比对expr和ParamType的类型,来推导出T的类型.

1.1 形参为T&类型

举例:

//  this is our template,
template<typename T>
void f(T& param);  // param is a reference

// and we have these variable declarations,
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

// the deduced types for param and T
f(x);   // T is int, param's type is int&

f(cx);  // T is const int,
        // param's typeis const int&

f(rx);  // T is const int,
        // param's typeis const int&

总结:
形参声明为指针或引用,但不是通用引用时,模板类型T的推导方法为忽略实参的reference-ness属性,保留实参的constness属性.

1.2 形参为const T&类型

上面的例子中, 模板函数f()的形参是左值引用,实际上当f()的形参是右值引用时,f()接受的实参必须为右值,但上面的类型推导方法依然生效.

如果我们把f()的形参类型从T&改成const T&, 则T的推导结果会有写不同.这种情况下,其实我们已经声明param的类型是一reference-to-const,所以在推导T的类型时,就没有必要再将const属性作为T的一部分了, 所以这种情况下推导出的T是没有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的类型时,rx的reference属性也被忽略了.

如果我们把param的类型从引用改成指针,T的类型推导结果是类似的:

template<typename T>
void f(T* param);   // param is now a pointer

int x = 27;         // as before
const int *px = &x; // px is a ptr to x as a const int

f(&x); // T is int, param's type is int*
f(px); // T is const int, param's typeis const int*

Case2: 形参声明是通用引用(T&&)

当函数模板的形参类型为T&&时,我们将其看做右值引用,则T的推导会与前面的规则有些不同:

  • 如果expr(实参)为左值, 则TParamType都会被推导为左值引用.
  • 如果expr(实参)为右值, 则Case1的规则生效

举例:

template<typename T>
void f(T&& param); // param is now a universal reference

int x = 27;         // as before
const int cx = x;   // as before
const int& rx = x;  // as before

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 typeis therefore int&&

总结:
当函数模板的形参类型为T&&时,T的推导类型与实参的类型有关:
- T会保留实参的constness属性.
- 当传入的实参为左值时,T的类型是左值引用, 当传入的实参为右值时, T的类型是右值引用.


Case3: 形参既不是指针也不是引用

当ParamType既不是指针也不是引用时, 那么我们要处理的实际是pass-by-value的问题.这就意味着,param是copy来的,是一个新的对象.这种情况下,T的类型推导规则为:

  • 若传入的expr为引用,则忽略其reference-ness属性.
  • 忽略expr的constvolatile属性.

举例:

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

int x = 27;         // as before
const int cx = x;   // as before
const int& rx = x;  // as before

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

在上面的例子中,虽然cx和rx都具有const属性,但param不具有const属性,因为param是cx/rx的copy对象,与原对象cx/rx是完全独立的关系,所以对param的修改并不会影响到cx/rx.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值