【问题标题】:I'm having some difficulty interpreting bullet point (5.2.1.1) in paragraph §8.5.3/5 of N4140我在解释 N4140 第 8.5.3/5 段中的要点(5.2.1.1)时遇到了一些困难
【发布时间】:2015-03-06 14:45:25
【问题描述】:

下面的sn-p编译

#include <iostream>
int& f() { static int i = 100; std::cout << i << '\n'; return i; }

int main()
{
    int& r = f();
    r = 101;
    f();
}

并打印值 (live example)

100
101

现在,阅读 N4140 中的 §8.5.3/5,我可以看到它编译是因为要点 (5.1.1),即引用是左值引用,初始化表达式是左值和 @987654325 @ 与 int (或 int&amp; - 我不确定我应该在这里使用哪一个)引用兼容。

要点 (5.1) 和 (5.1.1):

——如果引用是左值引用和初始化表达式

 — is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with
   “cv2 T2,” or ...

现在假设我将声明int&amp; r = f(); 中的左值引用更改为右值引用,即int&amp;&amp; r = f();。我知道代码不会编译,因为右值引用不会绑定到左值。但我很好奇的是,如何使用标准得出这个结论?

我会解释我的困难:

  1. 显然int&amp;&amp; r = f(); 已被项目符号 (5.2) 所涵盖,因为该引用是一个右值引用。

要点(5.2):

— 否则,引用应为对 a 的左值引用 非易失性 const 类型(即 cv1 应为 const),或引用 应该是一个右值引用。

  1. 原则上,我会说 (5.2.1.1) 支持这种初始化,因为初始化程序是一个函数左值并且intint(或int&amp;)的引用兼容。

要点 (5.2.1) 和 (5.2.1.1):

——如果初始化表达式

— is an xvalue (but not a bit-field), class prvalue, array prvalue or function
  lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or ...

编辑

我已经逐字包含了 N4140 (C++14) 中的项目符号点,它们等同于 N3337 (C++11) 中的类似项目符号点。

【问题讨论】:

  • f() 不是函数左值。它是一个函数调用表达式,一个左值表达式,类型为int。见[expr.call]。 5.2.2.2 适用,然后 5.2.2.4 使其格式错误。
  • @dyp 但是什么是函数左值?我刚刚注意到标准没有定义这个术语。
  • 它是值类别“左值”和类型some function type的表达式。一个例子是 id 表达式f。例如。你可以写using ft = int&amp;(); ft&amp; lvalue_ref_to_function = f;
  • 我认为function lvalues出现在该段中的原因是没有函数类型的rvalue-expressions。例如。 [expr.static.cast]/1 表示表达式static_cast&lt;ft&amp;&amp;&gt;(f) 是一个左值,尽管目标类型有右值引用。
  • @dyp 您的最后一条评论非常棒,尽管我会说ft&amp; lvalue-ref_to_function = f; 侵犯了要点 (5.2.1.1),因为此时左值必须是 const per (5.2) 和更多 @ 987654342@ 也为函数 g 编译返回一个右值引用。请参阅此处 (coliru.stacked-crooked.com/a/43a4c351a58ab24b)。

标签: c++ reference initialization language-lawyer c++14


【解决方案1】:

初始化表达式是一个左值,intint (或 int&amp; - 我不确定我应该在这里使用哪一个)引用兼容。

引用兼容性是应用于所引用类型的关系,而不是引用类型。例如,[dcl.init.ref]/5 讨论了“通过 cv2 T2 类型的表达式对类型 cv1 T1 的引用”,以及稍后比较例如“其中T1T2 没有引用相关”。

表达式f() 的类型只是int,尽管f 的返回类型是int&amp;。当我们观察表达式时,表达式根本没有引用类型(*);引用被剥离并用于确定值类别(参见 [expr]/5)。对于int&amp; f(),表达式f() 是一个左值;对于int g(),表达式g() 是一个右值。

(*)准确地说,表达式can have reference type in the Standard,但仅作为“初始”结果类型。引用在“任何进一步分析之前”被删除,这意味着这个引用根本无法通过类型观察到。


现在假设我将声明int&amp; r = f(); 中的左值引用更改为右值引用,即int&amp;&amp; r = f();。我知道代码不会编译,因为右值引用不会绑定到左值。但我很好奇的是,如何使用标准得出这个结论?

从 cmets 中的讨论看来,混淆似乎是 f() 不是函数左值。诸如“左值”和“右值”之类的值类别是表达式的属性。因此,术语“函数左值”必须指代一个表达式,即具有值类别“左值”的函数类型的表达式

但表达式f() 是一个函数调用表达式。从语法上讲,它是一个后缀表达式,后缀是函数参数列表。根据 [expr.call]/10:

如果结果类型是左值引用类型或对函数类型的右值引用,则函数调用是左值;如果结果类型是对对象类型的右值引用,则为 xvalue,否则为纯右值。

还有 [expr.call]/3

如果后缀表达式指定了一个析构函数[...];否则,函数调用表达式的类型是静态选择函数的返回类型 [...]

即表达式f()的(观察到的见上)类型为int,值类别为“左值”。请注意,(观察到的)类型是not int&amp;

函数左值例如是一个id-expression,如f,间接函数指针的结果,或产生任何类型函数引用的表达式:

using ft = void();
void f();
ft&  l();
ft&& r();
ft*  p();

// function lvalue expressions:
f
l()
r()
*p()

[expr.prim.general]/8 指定像f 这样的标识符是,作为id-expressions,左值:

identifier 是一个 id-expression,前提是它已被适当地声明。 [...] 表达式的类型是标识符的类型。结果是标识符表示的实体。如果实体是函数、变量或数据成员,则结果为左值,否则为纯右值。


回到示例int&amp;&amp; r = f();。使用一些 N4296 后的草稿。

[dcl.init.ref]

5 对类型“cv1T1”的引用由类型为的表达式初始化 “cv2T2”如下:

  • (5.1) 如果引用是左值引用和初始化表达式

引用是一个右值引用。 5.1 不适用。

  • (5.2) 否则,引用应为对 非易失 const 类型(即 cv1 应为 const),或引用 应该是一个右值引用。 [省略示例]

这适用,引用是右值引用。

  • (5.2.1) 如果初始化表达式
    • (5.2.1.1) 是一个 xvalue(但不是位域)、类纯右值、数组纯右值或函数左值和 [...],或者
    • (5.2.1.2) 具有类类型(即,T2 是类类型)[...]

初始化器是int 类型的左值。 5.2.1 不适用。

  • (5.2.2) 否则:
    • (5.2.2.1) 如果 T1T2 是类类型 [...]
    • (5.2.2.2) 否则,将创建一个临时类型的“cv1 T1”并从初始化表达式复制初始化 (dcl.init)。然后将引用绑定到临时文件。

最后,5.2.2.2 适用。然而:

如果T1T2 引用相关:

  • (5.2.2.3) cv1 的 cv 限定应与 cv2 相同或更高;和
  • (5.2.2.4) 如果引用是右值引用,则初始化表达式不应是左值。

T1T2intf() 的返回类型的引用被移除,仅用于确定值类别),因此它们是引用相关的。 cv1cv2 都是空的。 引用是右值引用,f() 是左值,因此 5.2.2.4 呈现程序格式错误。


“函数左值”一词出现在 5.2.1.1 中的原因可能与“函数右值”的问题有关(例如,参见N3010 - Rvalue References as "Funny" Lvalues)。 C++03 中没有函数右值,而且委员会似乎不想在 C++11 中引入它们。如果没有右值引用,我认为不可能获得函数右值。例如,您不能强制转换为函数类型,也不能从函数返回函数类型。

可能为了一致性,函数左值可以通过强制转换绑定到对函数类型的右值引用:

template<typename T>
void move_and_do(T& t)
{
    T&& r = static_cast<T&&>(t); // as if moved
}

int i = 42;
move_and_do(i);

move_and_do(f);

但是对于T 是像void() 这样的函数类型,static_cast&lt;T&amp;&amp;&gt;(t) 的值类别是左值(没有函数类型的右值)。因此,对函数类型的右值引用可以绑定到函数左值。

【讨论】:

  • @dyp 太好了。严谨,没有使解释过于复杂。不要再从我这里吹毛求疵了。
猜你喜欢
  • 2019-08-09
  • 2019-09-12
  • 1970-01-01
  • 1970-01-01
  • 2012-10-31
  • 2012-07-15
  • 2021-08-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多