模板中左值(&)/右值(&&)引用绑定与引用折叠规则及案例解析
核心规则
1. 引用绑定基础规则
- 左值引用(
T&):仅可绑定到可修改的左值(如命名变量、返回左值引用的表达式),无法直接绑定右值(临时对象、字面量等);const T&是例外,可绑定任意左/右值,但本文案例均为非const引用。 - 右值引用(
T&&):仅可绑定到右值;但如果是函数模板参数推导语境下的TYPE&&,则为万能引用——会根据传入实参的类型推导TYPE,再通过引用折叠确定最终引用类型。
2. 引用折叠规则
所有引用组合的折叠结果遵循“左值优先”原则:只要参与折叠的引用中有一个是左值引用,结果即为左值引用;仅当两个都是右值引用时,结果才是右值引用:
T& &→T&T& &&→T&T&& &→T&T&& &&→T&&
案例问题拆解
问题1:为何B b2{int(1)}(case4)合法,而case2、5、6、7不合法?
逐个分析相关案例:
case5:int& ii{int(12)};(Error)
直接用临时右值初始化非const左值引用,违反左值引用绑定规则,编译报错。
case6:int& iii{std::move(int(12))};(Error)
std::move(int(12))仅将临时右值转换为右值引用类型,但本质仍为右值,非const左值引用无法绑定右值,编译报错。
case2:A a2{int(1)};(标注OK)
A的构造函数参数int&& pi绑定到临时右值int(1),成员int& i绑定的是pi——所有命名的引用变量都是左值,因此i绑定左值pi是合法的。注意:pi引用的临时对象会在构造函数执行完毕后销毁,a2.i会成为悬垂引用,属于运行时未定义行为,但编译阶段仅检查绑定规则,因此标注为OK。
case7:C c1{i};(Error)
C是类模板,C c1{i}会推导类模板参数TYPE为int,此时构造函数参数为int&& pi——这是普通右值引用,而非万能引用(万能引用仅存在于函数模板参数推导语境,类模板参数确定后,函数参数类型固定)。左值i无法绑定到右值引用int&&,编译报错。
case4:B b2{int(1)};(OK)
B的构造函数是函数模板:template<typename TYPE> B(TYPE&& pi),这里的TYPE&&是万能引用。传入右值int(1)时,推导TYPE为int,TYPE&&折叠为int&&,pi绑定到临时右值。成员int& i绑定到pi(命名右值引用是左值),符合左值引用绑定规则,编译合法。
问题2:为何case9合法,而case10、11不合法?
case9:C<int&> c3{i};(OK)
显式指定类模板参数TYPE=int&,构造函数参数TYPE&& pi即int& && pi,根据引用折叠规则,int& && → int&,因此构造函数参数实际为int& pi,可绑定左值i。成员TYPE& i即int& & i,折叠为int& i,绑定到左值pi,符合规则。
case10:C<int&> c4{int(1)};(Error)
显式指定TYPE=int&后,构造函数参数折叠为int& pi,需要绑定左值,但传入的int(1)是右值,左值引用无法绑定右值,编译报错。
case11:int&& iiii{ir};(Error)
ir是左值引用,本身属于左值;右值引用int&&仅可绑定右值,无法绑定左值,因此编译报错。此处无模板推导或引用折叠,仅为普通右值引用的绑定规则问题。
关键区分:万能引用与普通右值引用
万能引用的核心判定条件:
- 仅出现在函数模板参数推导语境中;
&&所依附的类型参数是被推导的未限定类型(如template<typename T> void func(T&&))。
若&&是类模板参数的一部分(如C(TYPE&& pi)中TYPE是类模板参数),则为普通右值引用(或折叠后的引用),无参数推导过程。
内容的提问来源于stack exchange,提问作者AKL

