当前位置: 首页 > news >正文

条款1:理解模版性别推导

目录

问题引出

情况1:ParamType是个指针或引用,但不是个万能引用。

情况2:ParamType是个万能引用

情况3:ParamType既非指针也非引用


问题引出

函数模板大致形如:

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

而一次调用则形如:

f(expr)  //以某表达式调用f

在编译期,编译器会通过expr推导两个型别:一个是T的型别,另一个是ParamType的型别,这两个型别往往不一样。因为,ParamType常会包含了一些饰词,如const或引用符号等限定词。例如,若模板声明如下:

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

而调用语句如下:

int x = 0;

f(x);     //以一个int调用f

在此例中,T被推导为int,而ParamType则被推导为const int&。

我们很自然地会认为,T的型别推导结果和传递给函数的实参型别是同一的。换句话说,T的型别就是expr的型别。在上例中,情况确乎如此:x的型别是int,T的型别也推导为int。但是,这一点并不总是成立。T的型别推导结果,不仅仅依赖于expr的型别,还依赖ParamType的形式。具体要分三种情况讨论:

  • ParamType具有指针或引用型别,但不是个万能引用。
  • ParameType是一个万能引用。
  • ParamType既非指针也非引用。

这么一来,我们就有了三种型别推导场景进行分情况考察。在对它们逐一考察时,我们仍采用前述模板和调用的一般形式。

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

f(expr);  //从expr来推导T和ParamType的型别

情况1:ParamType是个指针或引用,但不是个万能引用。

在这种情况下,型别推导会这样运作:

  1. 若expr具有引用型别,先将引用部分忽略。
  2. 尔后,对expr的型别和ParamType的型别执行模式匹配,来决定T的型别。

例如,我们的模式如下:

template<typename T>
void f(T& param);     // param是个引用

又声明了下列变量:

int x = 27;          // x的型别是int
const int cx = x;    // cx的型别是const int
const int& rx = x;   // rx是x的型别为const int的引用

在各次调用中,对param和T的型别推导结果如下:

f(x);    // T的型别是int,param的型别是int&

f(cx);  // T的型别是const int,param的型别是const int&

f(rx);  // T的型别是const int,param的型别是const int&

在第二个以及第三个调用语句中,由于cx和rx的值都被指明为const,所以T的型别被推导为const int,从而形参的型别就成了const int&。这一点对于调用者来说至关重要。当人们向引用型别的形参传入const对象时,它们期望该对象保持其不可修改的属性,也就是说,期望该形参成为const的引用型别。这也是为何向持有  T& 型别的模板传入 const 对象是安全的:该对象的常量性(constness)会成为T的型别推导结果的组成部分。

在第三个调用中,请注意,即使rx具有引用型别,T也并未被推导成一个引用。原因在于,rx的引用性(reference-ness)会在型别推导过程中被忽略。

尽管上述调用语句示例演示的都是左值引用形参,但是右值引用形参的型别推导运作方式是完全相同的。当然,传给右值引用形参的,只能是右值引用实参,但这个限定和型别推导无关。

如果我们将形参型别从 T& 改为 const T& ,结果会有一点变化,但这些变化并没有什么出人意料之处。cx和rx的常量性仍然得到了满足,但是由于我们现在会假定param具有const应用型别,T的型别推导结果中包含 const 也就没有必要了。

template<typename T>
void f(const T& param);    // param现在是个const引用了

int x = 27;                // 同前
const int cx = x;          // 同前
const int& rx = x;         // 同前
 
f(x);                      // T的型别是int,param的型别是const int&
f(cx);                     // T的型别是int,param的型别是const int&
f(rx);                     // T的型别是int,param的型别是const int&

一如前例,rx的引用性在型别推导过程中是被忽略的。

如果 param 是个指针(或指涉到 const 对象的指针)而非引用,运作方式本质上并无不同:

template<typename T>
void f(T* param)      // param 现在是个指针了

int x = 27;           // 同前
const int *px = &x;   // px是指涉到x的指针,型别为const int

f(&x);                // T的型别是int,param的型别是int*
f(px);                // T的型别是const int,param的型别是const int*

情况2:ParamType是个万能引用

对于持有万能引用形参的模板而言,规则就不那么明显了。此类形参的声明方式类似右值引用(即在函数模板中持有型别形参T时,万能引用的声明型别写作 T&& ),但是当传入的实参是左值时,其表现会有所不同。

  • 如果 expr 是个左值,T 和 ParamType都会被推导为左值引用。这个结果具有双重奇特之处:首先,这是在模板型别推导中,T被推导为引用型别的唯一情形。其次,尽管在声明时使用的是右值引用语法,它的型别推导结果却是左值引用。
  • 如果 expr 是个右值,则应用“常规”(即情况1中的)规则。

例如:

template<typename T>
void f(T&& param);     // param现在是个万能引用

int x = 27;            // 同前
const int cx = x;      // 同前
const int& rx = x;     // 同前

f(x);                  // x是个左值,所以 T 的型别是 int&,param的型别也是int&

f(cx);                 // cx是个左值,所以T的型别是const int&,
                       // param的型别也是const int&

f(rx);                 // rx是个左值,所以T的型别是const int&,
                       // param的型别也是const int&

f(27);                 // 27是个右值,所以T的型别是int,
                       // 这么一来,param的型别就成了 int&&

关键之处在于,万能引用形参的型别推导不同于左值引用和右值引用形参。具体地,当遇到万能引用时,型别推导规则会区分实参是左值还是右值。而非万能引用时从来不会作这样的区分的。

情况3:ParamType既非指针也非引用

当ParamType既非指针也非引用时,我们面对的就是所谓按值传递了:

template<typename T>
void f(T param);     //param现在是按值传递

这意味着,无论传入的是什么,param都会是它的一个副本,也即一个全新对象。param会是个全新对象这一事实促成了如何从expr推导T的型别的规则:

  • 一如之前,若expr具有引用型别,则忽略其引用部分。
  • 忽略expr的引用性之后,若expr是个const对象,也忽略之。若其是个 volatile 对象,同忽略之(volatile对象不常用,它们一般仅用于实现设备驱动程序。)

所以,

int x = 27;          // 同前
const int cx = x;    // 同前
const int &rx = x;   // 同前

f(x);                // T和param的型别都是int
f(cx);               // T和param的型别还都是int
f(rx);               // T和param的型别仍都是int

请注意,即使cx和rx代表const值,param仍然不具有const型别。这是合理的。param是个完全独立于cx和rx存在的对象——是cx和rx的一个副本。从而cx和rx不可修改这一事实并不能说明param是否可以修改。正是由于这一原因,expr的常量性以及挥发性(volatileness,若有)可以在推导param的型别时加以忽略:仅仅由于expr不可修改,并不能断定其副本也不可修改。

需要重点说明的是,const(和volatile)仅会在按值形参处被忽略。正如此前所见,若形参是const的引用或指针,expr的常量性会在型别推导过程中加以保留。但是考虑这种情况:expr是个指涉到const对象的const指针,且expr按值传给param:

template<typename T>
void f(T param);         // param仍按值传递

const char* const ptr = "Fun with pointers";  // ptr是个指涉到const对象的const指针

f(ptr);                  // 传递型别为const char * const的实参

这里位于星号右侧的const将ptr声明为const:ptr不可以指涉到其他内存位置,也不可以被置为null【位于星号左侧的const则将ptr指涉到的对象(那个字符串)为const,即将字符串不可修改】。可ptr被传递给f时,这个指针本身将会按比特复制给param。换言之,ptr这个指针自己会被按值传递。依照按值传递形参的型别推导规则,ptr的常量性会被忽略,param的型别会被推导为const char *,即一个可修改的、指涉到一个const字符串的指针。在型别推导的过程中,ptr指涉到的对象的常量性会得到保留,但其自身的常量性则会以复制方式创建新指针param的过程中被忽略。

相关文章:

  • C#带多组标签的Snowflake SQL查询批量数据导出程序
  • linux 命令 grep
  • Embedding模型到底是什么?
  • C++11 编译使用 aws-cpp-sdk
  • 专题地图的立体表达-基于QGIS和PPT的“千层饼”视图制作实践
  • 后端主流数据库分析
  • 前端面试:React生态有哪些?
  • 【从零开始学习计算机科学】数据库系统(八)数据库的备份和恢复
  • 神经网络常用库-torch(基础操作张量)
  • 奇墨科技FinOps云成本优化:精细化IT成本分摊重塑企业云财务管理
  • JavaScript class
  • Axure设计之下拉多选框制作教程C(中继器)
  • 网络安全防护架构有哪些 网络安全防护措施包括
  • 上下文学习思维链COTPrompt工程
  • SpringMVC响应页面及不同类型的数据,
  • [LeetCode热门100题]|137,260,268,面试17.19
  • vs-code + nRF Connect SDK 编译nrf54l15dk
  • 系统思考:销售业绩与团队士气
  • AD9850函数信号发生器制作(全套资料)
  • C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷一)
  • 做网站需要准备什么东西/济南竞价托管公司
  • 汽车4s销售网站模板/seo代码优化步骤
  • 找人做网站被骗/seo顾问是什么
  • 孟村县做网站/昆明自动seo
  • 苏州相城做网站哪家好/营销方案ppt
  • 企业公司网站建设方案/山东进一步优化