What are the rules of rvalue(&&) lvalue(&) reference binding in templates with regard to reference collapsing?

huangapple go评论82阅读模式
英文:

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&amp; ir{i};

class A{
public:
	int&amp; i;
	A(int&amp;&amp; pi):i(pi){}
};

A a1{i}; // Error // case 1
A a2{int(1)}; // OK // case 2

class B{
public:
	int&amp; i;
	template&lt;typename TYPE&gt;
	B(TYPE&amp;&amp; pi):i(pi){}
};

B b1{i}; // OK // case 3
B b2{int(1)}; //OK // case 4
int&amp; ii{int(12)}; // Error // case 5
int&amp; iii{std::move(int(12))}; // Error // case 6

template&lt;typename TYPE&gt;
class C{
public:
	TYPE&amp; i;
	C(TYPE&amp;&amp; pi):i(pi){}
};

C c1{i}; // Error // case 7
C c2{int(1)}; // OK // case 8
C&lt;int&amp;&gt; c3{i}; // OK // case 9
C&lt;int&amp;&gt; c4{int(1)}; // Error // case 10
int&amp;&amp; iiii{ir}; // Error // case 11

A rvalue can not be bound to lvalue and if my understanding is correct, TYPE&amp;&amp; would either collapse to TYPE&amp;&amp; or TYPE&amp;.
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&amp;&amp; is int&amp;&amp;&amp; which (I assume) collapses to int&amp; , then how could c3.i which is an rvalue(int&amp;&amp;) 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。我认为解释你列出的情况应该足以形成一个整体图像。

  1. rvalue引用 int&& pi 无法绑定到lvalue i
  2. rvalue引用 int&& pi 可以绑定到rvalue int(1)i(pi) 中的 pi 指的是一个内存位置,因此它是一个lvalue,允许初始化 a2.i。构造完成后,a2.i 是对 int(1) 的悬空引用。
  3. 转发引用 TYPE&& pi 可以绑定到lvalue iTYPEint&b1.i 指的是 i
  4. 转发引用 TYPE&& pi 可以绑定到rvalue int(1)TYPEintTYPE&& pi 是一个rvalue引用,类似于情况2,b2.i 是对 int(1) 的悬空引用。
  5. lvalue引用 int& ii 无法绑定到rvalue int(12)
  6. lvalue引用 int& iii 无法绑定到rvalue std::move(int(12))

在情况7-10中,TYPE&& pi 不是转发引用,因为TYPEclass C 的模板参数,而不是构造函数的模板参数。在7-8中用于推断 TYPE 的CTAD并没有改变这一点:预期的是一个rvalue,只有这样才能将 TYPE 推断为 int,形成 int&& pi

  1. 无法推断出 TYPE
  2. TYPE 被推断为 int。声明为 int& i;c2.i 形成了对 int(1) 的悬空引用。
  3. TYPE 明确为 int&TYPE&& pi 折叠为 int& piTYPE& i 折叠为 int& ic3.i 指的是 i
  4. TYPE&& pi 折叠为 int& pi,lvalue引用无法绑定到 int(1)
  5. rvalue引用 int&& iiii 无法绑定到lvalue ir

还有一个可能会使情况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.

  1. rvalue reference int&amp;&amp; pi can't be bound to lvalue i.
  2. rvalue reference int&amp;&amp; pi can be bound to rvalue int(1). pi in i(pi) refers to a memory location so it is an lvalue, allowing a2.i to be initialized. After construction a2.i is a dangling reference to int(1).
  3. forwarding reference TYPE&amp;&amp; pi can be bound to lvalue i. TYPE is int&amp;. b1.i refers to i.
  4. forwarding reference TYPE&amp;&amp; pi can be bound to rvalue int(1). TYPE is int. TYPE&amp;&amp; pi is an rvalue reference, similar to case 2 b2.i is a dangling reference to int(1).
  5. lvalue reference int&amp; ii can not be bound to rvalue int(12).
  6. lvalue reference int&amp; iii can not be bound to rvalue std::move(int(12)).

In cases 7-10 TYPE&amp;&amp; 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&amp;&amp; pi.

  1. TYPE can't be deduced.
  2. TYPE is deduced to be int. c2.i, declared as int&amp; i;, forms a dangling reference to int(1).
  3. TYPE is explicitly int&amp;. TYPE&amp;&amp; pi is collapsed to int&amp; pi. TYPE&amp; i is collapsed to int&amp; i. c3.i refers to i.
  4. TYPE&amp;&amp; pi is collapsed to int&amp; pi, lvalue reference can't be bound to int(1).
  5. rvalue reference int&amp;&amp; iiii can't be bound to lvalue ir.

And a bonus case that might make cases 2 and 4 easier to understand:

int&amp;&amp; rvr = 1;
int&amp;&amp; rvr2 = rvr;

The first line is correct, the last is an error. int&amp;&amp; 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&amp;&amp; rvr2 can't be bound to it.

huangapple
  • 本文由 发表于 2023年7月6日 18:26:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76627864.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定