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

I'm having some difficulty interpreting bullet point (5.2.1.1) in paragraph §8.5.3/5 of N4140

下面的代码片段编译

#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),即引用是左值引用,初始化表达式是左值并且intint 参考兼容(或 int& - 我不确定我应该在这里使用哪一个)。

要点 (5.1) 和 (5.1.1):

— If the reference is an lvalue reference and the initializer expression

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

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

我会解释我的困难是什么:

  1. 显然 int&& r = f(); 包含在要点 (5.2) 中,因为引用是右值引用。

要点 (5.2):

— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.

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

要点 (5.2.1) 和 (5.2.1.1):

— If the initializer expression

— 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) 中的类似要点。

the initializer expression is an lvalue and int is reference-compatible with int (or with int& - I don't know for sure which one I should use here).

引用兼容性是应用于引用类型的关系,而不是引用类型。例如,[dcl.init.ref]/5 谈到初始化 "a reference to type cv1 T1 by an expression of type cv2 T2",然后比较例如"where T1 is not reference-related to T2".

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

(*)准确地说,表达式 can have reference type in the Standard,但仅作为 "initial" 结果类型。引用被删除 "prior to any further analysis",这意味着这个引用根本无法通过类型观察到。


Now suppose I change the left value reference in the declaration int& r = f(); by a right value reference, i.e., int&& r = f();. I know the code won't compile, as an rvalue reference doesn't bind to an lvalue. But what I'm curious is, how to reach this conclusion using the Standard?

从评论中的讨论看来,混乱似乎是 f() 不是函数左值。 "lvalue" 和 "rvalue" 等值类别是表达式的属性。因此,术语 "function lvalue" 必须引用一个表达式,即具有值类别 "lvalue".

的函数类型的 表达式

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

A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.

和[expr.call]/3

If the postfix-expression designates a destructor [...]; otherwise, the type of the function call expression is the return type of the statically chosen function [...]

即表达式f()的(观察见上文)类型为int,取值类别为"lvalue" .请注意,(观察到的)类型是 not int&.

函数左值例如是 id 表达式,如 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,左值:

An identifier is an id-expression provided it has been suitably declared. [...] The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.


回到例子int&& r = f();。使用一些 post-N4296 草稿。

[dcl.init.ref]

5 A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

  • (5.1) If the reference is an lvalue reference and the initializer expression

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

  • (5.2) Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference. [example omitted]

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

  • (5.2.1) If the initializer expression
    • (5.2.1.1) is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and [...], or
    • (5.2.1.2) has a class type (i.e., T2 is a class type) [...]

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

  • (5.2.2) Otherwise:
    • (5.2.2.1) If T1 or T2 is a class type [...]
    • (5.2.2.2) Otherwise, a temporary of type “cv1 T1” is created and copy-initialized (dcl.init) from the initializer expression. The reference is then bound to the temporary.

最后,5.2.2.2 适用。然而:

If T1 is reference-related to T2:

  • (5.2.2.3) cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; and
  • (5.2.2.4) if the reference is an rvalue reference, the initializer expression shall not be an lvalue.

T1T2intf()的return类型的引用去掉,仅用于判断值类别) ,所以它们是参考相关的。 cv1cv2 都是空的。 引用是右值引用,f()是左值,因此 5.2.2.4 使程序格式错误。


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

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

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);

但是对于 Tvoid() 这样的函数类型,static_cast<T&&>(t) 的值类别是 lvalue (没有右值函数类型)。因此,对函数类型的右值引用可以绑定到函数左值。