我在解释N4140第§8.5.3/ 5段中的要点(5.2.1.1)时遇到了一些困难

Bel*_*loc 3 c++ initialization reference language-lawyer c++14

下面的代码片段汇编

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

int main()
{
    int& r = f();
    r = 101;
    f();
}
Run Code Online (Sandbox Code Playgroud)

并打印值(实例)

100
101
Run Code Online (Sandbox Code Playgroud)

现在,阅读N4140中的§8.5.3/ 5,我可以看到它由于子弹点(5.1.1)而编译,也就是说,引用是左值引用,初始化表达式是左值并且与int引用兼容int(或者int&- 我不确定我应该在这里使用哪一个).

子弹点(5.1)和(5.1.1):

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

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

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

我会解释一下我的困难:

  1. 显然int&& r = f();由子弹点(5.2)涵盖,因为参考是右值参考.

要点(5.2):

- 否则,引用应是对非易失性const类型的左值引用(即,cv1应为const),或者引用应为右值引用.

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

要点(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 ...
Run Code Online (Sandbox Code Playgroud)

编辑

我已经从N4140(C++ 14)中逐字列出了要点,它们相当于N3337(C++ 11)中类似的要点.

dyp*_*dyp 5

初始化表达式是一个左值并且与int引用兼容int(或者int&- 我不知道我应该在这里使用哪一个).

引用兼容性是应用于所引用类型的关系,而不是引用类型.例如,[dcl.init.ref]/5讨论初始化"通过类型cv2 T1的表达式对类型cv1的引用 T2 ",并且稍后比较例如"where T1not not related to related T2".

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

(*)为了非常精确,表达式可以在标准中具有引用类型,但仅作为"初始"结果类型.在"任何进一步分析之前"删除引用,这意味着该引用根本不能通过类型观察到.


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

从评论中的讨论看来,混淆似乎f()不是函数左值.诸如"左值"和"右值"之类的值类别是表达式的属性.因此,术语"函数左值"必须引用表达式,即具有值类别"左值"的函数类型表达式.

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

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

和[expr.call]/3

如果postfix-expression指定析构函数[...]; 否则,函数调用表达式的类型是静态选择函数的返回类型[...]

也就是说,表达式的(观察见上文)类型f()int,并且值类别是"左值".请注意,(观察到的)类型不是 int&.

函数左值是例如一个ID-表达类似f,indirecting函数指针,或产生任何种类的参考函数的表达式的结果:

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

// function lvalue expressions:
f
l()
r()
*p()
Run Code Online (Sandbox Code Playgroud)

[expr.prim.general]/8指定那些标识符喜欢f是,如ID-表达式,左值:

一个标识符是一个ID-表达,只要它已被适当地声明.[...]表达式的类型是标识符的类型.结果是由标识符表示的实体.如果实体是函数,变量或数据成员,则结果是左值,否则为prvalue.


回到例子int&& r = f();.使用一些后N4296草案.

[dcl.init.ref]

5参考输入" CV1 T1 "是通过类型"的表达初始化CV2 T2 "如下:

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

引用是右值引用.5.1不适用.

  • (5.2)否则,引用应是对非易失性const类型的左值引用(即,cv1应为const),或者引用应为右值引用.[例子省略]

这适用,引用是rvalue-reference.

  • (5.2.1)如果是初始化表达式
    • (5.2.1.1)是xvalue(但不是位字段),类prvalue,数组prvalue或函数lvalue和[...],或者
    • (5.2.1.2)有一个类类型(即T2类型)[...]

初始化程序是类型的左值int.5.2.1不适用.

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

最后,5.2.2.2适用.然而:

如果T1与参考相关T2:

  • (5.2.2.3)CV1应是相同的CV-资格,或更大的CV-资格比,CV2 ; 和
  • (5.2.2.4)如果引用是右值引用,则初始化表达式不应是左值.

T1T2int(的返回类型的参考f()被去除并且只用来确定值类别),所以他们参考相关.cv1cv2都是空的.引用是一个右值引用,并且f()是一个左值,因此5.2.2.4使程序格式错误.


术语"函数左值"出现在5.2.1.1中的原因可能与"函数rvalues"的问题有关(例如,参见N3010 - Rvalue References as"Funny"Lvalues).C++ 03中没有函数rvalues,似乎委员会不想在C++ 11中引入它们.没有右值引用,我认为获得函数rvalue是不可能的.例如,您可能无法转换为函数类型,并且您可能无法从函数返回函数类型.

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

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);
Run Code Online (Sandbox Code Playgroud)

但是对于T函数类型来说void(),值类别static_cast<T&&>(t)lvalue(没有函数类型的rvalues).因此,对函数类型的rvalue引用可以绑定到函数左值.