临时初始化和引用初始化

Temporary initialization and Reference initialization

我正在尝试了解引用初始化的方式。例如,我们来看一个典型的例子。

double val = 4.55;
const int &ref = val;

我可以想到上面代码片段中发生的事情的两种可能性。

可能性 1

通常的解释如下:

此处创建了一个类型为 int 且值为 4 的临时 (prvalue),然后将引用 ref 绑定到此temporary(prvalue) int 对象 而不是 直接绑定到变量 val。发生这种情况是因为右侧变量 val 的类型是 double 而在左侧我们有一个 int 的引用.但是为了绑定对变量的引用,类型 应该匹配 。此外,临时纯右值的寿命延长了。

可能性 2

我认为还有另一种可能发生的情况如下:

此处创建了类型为 int 且值为 4 的临时 (prvalue)。但是由于 const int &ref 需要一个 glvalue 而目前我们有一个 prvalue,临时物化开始,因此 prvalue 被转换为一个 xvalue。然后引用ref绑定到这个物化的xvalue(因为xvalue也是一个glvalue)而不是绑定到变量val直接。发生这种情况是因为右侧变量 val 的类型是 double 而在左侧我们有一个 int 的引用.但是为了绑定对变量的引用,类型 应该匹配 。此外,物化临时 xvalue 的生命周期得到延长。

我的问题是:

  1. 根据C++11标准,以上解释正确的是。我愿意接受上述 none 的解释是正确的,在这种情况下,正确的解释是什么。
  2. 根据C++17标准,以上解释正确的是。我愿意接受上述 none 的解释是正确的,在这种情况下,正确的解释是什么。
  3. 我也对上述两种可能性的第一步中的纯右值是否实际上是一个临时对象感到困惑?或者 xvalue 是实际对象。我的意思是我们是否有 2 个临时对象,例如第一个是由于“转换为 prvalue”,第二个是由于“prvalue 到 xvalue”转换(临时物质化)。或者我们只有一个临时的,这是由于“prvalue to xvalue”临时实现。

PS:我不是在寻找解决这个问题的方法。例如,我知道我可以简单地写: const double &ref = val;。我的目标是根据 C++11C++17 标准了解正在发生的事情。

val in const int &ref = val; 是左值,不是纯右值。即使它被转换为纯右值,这也不意味着在 C++11 或 C++17 中创建一个临时值。在 C++17 中,情况并非如此,因为只有 prvalue-to-xvalue 转换(以及一些与此处无关的特殊情况)会创建一个临时对象,而在 C++11 中,[conv.lval]/2 表示仅适用于 class types lvalue-to-rvalue conversion creates a temporary.

引用的初始化在[dcl.init.ref]中有解释。

在 C++11 中,之前的所有情况都失败了,因此根据 [dcl.init.ref]/5.2.2 创建了一个目标类型的临时对象,并由 copy-initialization 从初始化表达式中初始化并绑定了引用到那个临时的。在此 copy-initialization 中,左值 val 被转换为类型 double 的纯右值,然后转换为类型 int 的纯右值,这些步骤都不会创建额外的临时值。

在 C++17 中,所有情况都会失败,直到 [dcl.init.ref]/5.2.2,这表明初始化表达式首先被隐式转换为目标类型的纯右值,这并不意味着创建临时对象,并且然后应用临时物化转换(prvalue-to-xvalue 转换)并将引用绑定到结果,即引用临时的 xvalue。

最后总有一个临时的,就是根据[dcl.init.ref]中的规则创建的那个。

这是我对 C++17 的看法:

double val = 4.55;
const int &ref = val;

我们将 const int 引用绑定到由 val 表示的 double 类型的左值表达式。根据 the declaration of references;

Otherwise, the initializer expression is implicitly converted to a prvalue of type “T1”.

我们将表达式 val 转换为类型 int。注意这个隐式类型!转换符合 the draft。 接下来,

The temporary materialization conversion is applied, considering the type of the prvalue to be “cv1 T1”, ...

为了应用临时物化(也称为 prvalue -> xvalue 转换),我们必须具有完全匹配的类型,因此表达式 被附加转换为 const int 被认为是 const int,没有任何转换 (source)。值类别保持不变 (prvalue)。最后,

... and the reference is bound to the result.

我们有一个 T 类型的引用绑定到 T 类型的纯右值,这会导致临时物化 (source) 并且 val 被转换为 xvalue。创建的临时对象的类型为const int,值为4.

编辑:当然是为了回答您的问题。因此,从一开始,给定的可能性对于 C++17 来说在技术上都是不正确的。严格来说,prvalues 和 xvalues 都不是临时对象。相反,它们是表达式的值类别,表达式可能(或可能不)表示一个(临时)对象。所以从技术上讲,xvalues 表示临时对象,而 prvalues 则不是。你仍然可以说 xvalues 是临时对象,只要你知道你在说什么,我完全没问题。

让我们分别看看每种情况(C++11 与 C++17)。

C++11

来自 decl.init.ref 5.2.2:

Otherwise, a temporary of type “ cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization ([dcl.init]). The reference is then bound to the temporary.

需要注意的另一件事是来自 basic.lval#4:

Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types...

当应用于您的示例时,这意味着创建了一个 int 类型的临时变量,并使用 non-reference [=61] 的规则从初始化表达式 val 对其进行了初始化=].如此创建的临时 int 具有 prvalue if/when 的值类别用作表达式。

接下来,引用 ref 将绑定到创建的临时值 int,其值为 4。因此,

double val = 4.55;
const int &ref = val; // ref refers to temporary with value 4

C++17

来自 decl.init.ref 5.2.2.2:

Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.

应用于您的示例时,这意味着初始化表达式 val 隐式转换为 const int 类型的纯右值。现在我们现在有一个纯右值 const int。但是在应用临时物化之前,expr 6 开始说:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

这意味着在临时物化发生之前,纯右值 const int 被调整为纯右值 int

最后,应用临时实体化,并且 ref 绑定到结果 xvalue