英文:
What are the rules of rvalue(&&) lvalue(&) reference binding in templates with regard to reference collapsing?
问题
考虑以下情况:
int i{};
int& ir{i};
class A {
public:
int& i;
A(int&& pi) : i(pi) {}
};
A a1{i}; // 错误 // 情况 1
A a2{int(1)}; // 正确 // 情况 2
class B {
public:
int& i;
template<typename TYPE>
B(TYPE&& pi) : i(pi) {}
};
B b1{i}; // 正确 // 情况 3
B b2{int(1)}; // 正确 // 情况 4
int& ii{int(12)}; // 错误 // 情况 5
int& iii{std::move(int(12))}; // 错误 // 情况 6
template<typename TYPE>
class C {
public:
TYPE& i;
C(TYPE&& pi) : i(pi) {}
};
C c1{i}; // 错误 // 情况 7
C c2{int(1)}; // 正确 // 情况 8
C<int&&> c3{i}; // 正确 // 情况 9
C<int&&> c4{int(1)}; // 错误 // 情况 10
int&& iiii{ir}; // 错误 // 情况 11
右值不能绑定到左值,如果我理解正确,TYPE&&
将要么折叠成 TYPE&&
,要么折叠成 TYPE&
。
然而,我对这些情况特别是情况 4 的理解有些困难。如果情况 4 是正确的,那么意味着我们有一个引用 b2.i
,它是从临时值(右值)初始化的。那么为什么情况 2、5、6 和 7 是错误的呢?而且情况 9 正确,这意味着 TYPE&&
是 int&&&&
,我假设它会折叠成 int&
,那么为什么 c3.i
是一个右值(int&&
)可以从左值初始化,而情况 10 和 11 是错误的呢?
我希望有人能解释一下关于这个主题的一般规则,以及详细解释这些情况。
英文:
Consider these cases:
int i{};
int& ir{i};
class A{
public:
int& i;
A(int&& pi):i(pi){}
};
A a1{i}; // Error // case 1
A a2{int(1)}; // OK // case 2
class B{
public:
int& i;
template<typename TYPE>
B(TYPE&& pi):i(pi){}
};
B b1{i}; // OK // case 3
B b2{int(1)}; //OK // case 4
int& ii{int(12)}; // Error // case 5
int& iii{std::move(int(12))}; // Error // case 6
template<typename TYPE>
class C{
public:
TYPE& i;
C(TYPE&& pi):i(pi){}
};
C c1{i}; // Error // case 7
C c2{int(1)}; // OK // case 8
C<int&> c3{i}; // OK // case 9
C<int&> c4{int(1)}; // Error // case 10
int&& iiii{ir}; // Error // case 11
A rvalue can not be bound to lvalue and if my understanding is correct, TYPE&&
would either collapse to TYPE&&
or TYPE&
.
How ever I am having a hard time understanding these cases, specially case 4. If case 4 is correct, then it means we have b2.i
which is a reference, initialized from a temporary (rvalue). Then why case 2, 5, 6 and 7 are incorrect? And when case 9 is correct it mean the TYPE&&
is int&&&
which (I assume) collapses to int&
, then how could c3.i
which is an rvalue(int&&
) be initialized from a lvalue, while case 10 and 11 are incorrect?
I wish some one could explain the general rules regarding this subject and also these cases in detail.
答案1
得分: 3
以下是翻译的内容:
这里有几个机制在起作用:lvalue/rvalue语义、引用折叠、转发引用和CTAD。我认为解释你列出的情况应该足以形成一个整体图像。
- rvalue引用
int&& pi
无法绑定到lvaluei
。 - rvalue引用
int&& pi
可以绑定到rvalueint(1)
。i(pi)
中的pi
指的是一个内存位置,因此它是一个lvalue,允许初始化a2.i
。构造完成后,a2.i
是对int(1)
的悬空引用。 - 转发引用
TYPE&& pi
可以绑定到lvaluei
。TYPE
是int&
。b1.i
指的是i
。 - 转发引用
TYPE&& pi
可以绑定到rvalueint(1)
。TYPE
是int
。TYPE&& pi
是一个rvalue引用,类似于情况2,b2.i
是对int(1)
的悬空引用。 - lvalue引用
int& ii
无法绑定到rvalueint(12)
。 - lvalue引用
int& iii
无法绑定到rvaluestd::move(int(12))
。
在情况7-10中,TYPE&& pi
不是转发引用,因为TYPE
是 class C
的模板参数,而不是构造函数的模板参数。在7-8中用于推断 TYPE
的CTAD并没有改变这一点:预期的是一个rvalue,只有这样才能将 TYPE
推断为 int
,形成 int&& pi
。
- 无法推断出
TYPE
。 TYPE
被推断为int
。声明为int& i;
的c2.i
形成了对int(1)
的悬空引用。TYPE
明确为int&
。TYPE&& pi
折叠为int& pi
。TYPE& i
折叠为int& i
。c3.i
指的是i
。TYPE&& pi
折叠为int& pi
,lvalue引用无法绑定到int(1)
。- rvalue引用
int&& iiii
无法绑定到lvalueir
。
还有一个可能会使情况2和4更容易理解的额外情况:
int&& rvr = 1;
int&& rvr2 = rvr;
第一行是正确的,最后一行是错误的。int&& rvr
是一个rvalue引用,并延长了临时对象的生命周期。但是在最后一行中提到的 rvr
是一个lvalue,因此rvalue引用 int&& rvr2
无法绑定到它。
英文:
There are several mechanisms at play here: lvalue/rvalue semantics, reference collapsing, forwarding reference and CTAD. I think explaining the cases you listed should be sufficient to form a big picture.
- rvalue reference
int&& pi
can't be bound to lvaluei
. - rvalue reference
int&& pi
can be bound to rvalueint(1)
.pi
ini(pi)
refers to a memory location so it is an lvalue, allowinga2.i
to be initialized. After constructiona2.i
is a dangling reference toint(1)
. - forwarding reference
TYPE&& pi
can be bound to lvaluei
.TYPE
isint&
.b1.i
refers toi
. - forwarding reference
TYPE&& pi
can be bound to rvalueint(1)
.TYPE
isint
.TYPE&& pi
is an rvalue reference, similar to case 2b2.i
is a dangling reference toint(1)
. - lvalue reference
int& ii
can not be bound to rvalueint(12)
. - lvalue reference
int& iii
can not be bound to rvaluestd::move(int(12))
.
In cases 7-10 TYPE&& pi
is not a forwarding reference because TYPE
is a template parameter of class C
, not of constructor. CTAD, used in 7-8 to deduce TYPE
, doesn't change that: an rvalue is expected, only then TYPE
can be deduced to int
, forming int&& pi
.
TYPE
can't be deduced.TYPE
is deduced to beint
.c2.i
, declared asint& i;
, forms a dangling reference toint(1)
.TYPE
is explicitlyint&
.TYPE&& pi
is collapsed toint& pi
.TYPE& i
is collapsed toint& i
.c3.i
refers toi
.TYPE&& pi
is collapsed toint& pi
, lvalue reference can't be bound toint(1)
.- rvalue reference
int&& iiii
can't be bound to lvalueir
.
And a bonus case that might make cases 2 and 4 easier to understand:
int&& rvr = 1;
int&& rvr2 = rvr;
The first line is correct, the last is an error. int&& rvr
is an rvalue reference and extends lifetime of a temporary. But rvr
mentioned in the last line is an lvalue, so rvalue reference int&& rvr2
can't be bound to it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论