我在解释 N5.5.3/5 第 4140 段中的项目符号 (5.2.1.1) 时遇到一些困难

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

本文关键字:符号 遇到 项目 N5 解释 段中 4140      更新时间:2023-10-16

下面的代码段编译

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

并打印数值(实际示例)

100
101

现在,阅读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 ...

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

我将解释我的困难:

  1. 很明显,int&& r = f();被要点(5.2)覆盖,因为引用是右值引用

要点(5.2):

否则,引用应为对非易失性常量类型(即,cv1应为常量),或引用应为右值参考。

  1. 原则上,我认为(5.2.1.1)支持这种初始化,因为初始化器是一个函数左值,int是与int(或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 ...

编辑

我已经逐字逐句地包含了N4140(C++14)中的要点,它们相当于N3337(C++11)中的类似要点。

初始值设定项表达式是一个左值,intint(或与int&——我不确定应该在这里使用哪一个)是引用兼容的。

引用兼容性是应用于引用类型的关系,而不是引用类型。例如,[dcl.init.ref]/5谈到初始化"通过类型cv2T2的表达式对类型cv1T1的引用",然后比较例如"其中T1不是与T2相关的引用"。

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

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


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

从评论中的讨论中可以看出,令人困惑的是f()似乎不是函数的左值。诸如"左值"answers"右值"之类的值类别是表达式的属性。因此,术语"函数左值"必须指代一个表达式,即值类别为"左值"的函数类型的表达式。

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

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

和[expr.call]/3

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

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

例如,函数左值是一个类似fid表达式,是函数指针的间接结果,或者是一个对函数产生任何引用的表达式:

using ft = void();
void f();
ft&  l();
ft&& r();
ft*  p();
// function lvalue expressions:
f
l()
r()
*p()

[expr.prim.general]/8指定像f这样的标识符是,作为id表达式,lvalues:

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


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

[dcl.init.ref]

5对类型">cv1T1"的引用由类型的表达式初始化">cv2T2"如下:

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

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

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

这适用于,引用是一个右值引用。

  • (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)如果T1T2是类类型[…]
    • (5.2.2.2)否则,将创建一个类型为">cv1T1"的临时值,并从初始值设定项表达式复制初始化(dcl.init)。然后将引用绑定到临时

最后,5.2.2.2适用。但是:

如果T1是与T2相关的引用:

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

T1T2int(f()返回类型的引用被删除,仅用于确定值类别),因此它们与引用相关cv1cv2均为空引用是一个右值引用,而f()是一个左值,因此5.2.2.4会导致程序格式错误


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

可能是为了一致性,可以通过强制转换将函数lvalues绑定到对函数类型的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);

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