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

时间:2015-01-08 13:09:50

标签: c++ reference initialization 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();
}

并打印值(live example

100
101

现在,阅读N4140中的§8.5.3/ 5,我可以看到它由于项目符号(5.1.1)而编译,也就是说,引用是左值引用,初始化表达式是左值和{{ 1}}与int(或与int的参考兼容 - 我不确定我应该在这里使用哪一个。)

子弹点(5.1)和(5.1.1):

  

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

int&

现在假设我通过正确的值引用更改了声明 — is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or ... 中的左值引用,即int& r = f();。我知道代码不会编译,因为右值引用不会绑定到左值。但我很好奇的是,如何使用标准来达成这个结论?

我会解释我的困难:

  1. 显然int&& r = f();由项目符号(5.2)涵盖,因为引用是右值引用。
  2. 要点(5.2):

      

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

    1. 原则上,我会说(5.2.1.1)支持这种初始化,因为初始化器是一个函数左值,int&& r = f();int(或int)的引用兼容。
    2. 要点(5.2.1)和(5.2.1.1):

        

      - 如果是初始化表达式

      int&

      修改

      我已经从N4140(C ++ 14)逐字记录了这些要点,这些要点相当于N3337(C ++ 11)中类似的要点。

1 个答案:

答案 0 :(得分:5)

  

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

引用兼容性是应用于所引用类型的关系,而不是引用类型。例如,[dcl.init.ref] / 5通过类型 cv2 {的表达式谈论初始化&#34;对 cv1 T1类型的引用{1}}&#34;,后来比较例如&#34;其中T2T1&#34;无参考相关。

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

(*)完全准确地说,表达式can have reference type in the Standard,但仅作为&#34;首字母&#34;结果类型。在进行任何进一步的分析之前,该引用被删除&#34;这意味着该引用根本不能通过类型观察到。


  

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

从评论中的讨论看来,混淆似乎是int&& r = f();不是函数左值。值类别如&#34;左值&#34;和&#34; rvalue&#34;是表达式的属性。术语&#34;功能左值&#34;因此必须引用一个表达式,即函数类型的表达式,其值类别为&#34;左值&#34;

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

  

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

和[expr.call] / 3

  

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

的返回类型

即表达式f()的(观察到见上面)类型是f(),值类别是&#34;左值&#34;。请注意,(观察到的)类型 int

函数lvalue例如是像<{1}}这样的 id-expression ,是间接函数指针的结果,或者是对函数产生任何类型的引用的表达式:

int&

[expr.prim.general] / 8指定像f这样的标识符,如 id-expressions ,lvalues:

  

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


回到示例using ft = void(); void f(); ft& l(); ft&& r(); ft* p(); // function lvalue expressions: f l() r() *p() 。使用一些后N4296草案。

  

[dcl.init.ref]

     

5 对类型“ cv1 f”的引用由类型表达式初始化   “ cv2 int&& r = f();”如下:

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

参考是右值参考。 5.1不适用。

  
      
  • (5.2)否则,引用应为a的左值引用   非易失性const类型(即 cv1 应为T1)或参考    应为右值参考。 [示例省略]
  •   

这适用,引用是rvalue-reference。

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

初始值设定项是const类型的左值。 5.2.1不适用。

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

最后,5.2.2.2适用。但是:

  

如果T2T1的参考相关:

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

T1T2T1T2的返回类型的引用已被删除,仅用于确定值类别),因此他们可以使用int。与参考有关。 cv1 cv2 都是空的。 引用是左值引用,f()是左值,因此5.2.2.4会使程序格式错误。


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

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

f()

但是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()的值类别是 lvalue (没有函数类型的右值)。因此,对函数类型的rvalue引用可以绑定到函数lvalues。