下面的代码段编译
#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();
。我知道代码不会编译,因为右值引用不会绑定到左值。但我很好奇的是,如何使用标准来达成这个结论?
我会解释我的困难:
int&& r = f();
由项目符号(5.2)涵盖,因为引用是右值引用。要点(5.2):
- 否则,引用应该是对a的左值引用 非易失性const类型(即cv1应为const)或参考 应为右值参考。
int&& r = f();
与int
(或int
)的引用兼容。 要点(5.2.1)和(5.2.1.1):
- 如果是初始化表达式
int&
修改
我已经从N4140(C ++ 14)逐字记录了这些要点,这些要点相当于N3337(C ++ 11)中类似的要点。
答案 0 :(得分:5)
初始化表达式是左值,
int
与int
(或与int&
引用兼容 - 我不确定我应该在哪里使用)
引用兼容性是应用于所引用类型的关系,而不是引用类型。例如,[dcl.init.ref] / 5通过类型 cv2 {的表达式谈论初始化&#34;对 cv1 T1
类型的引用{1}}&#34;,后来比较例如&#34;其中T2
与T1
&#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
”的引用由类型表达式初始化 “ cv2int&& 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)如果
T2
或int
是班级类型[...]- (5.2.2.2)否则,将创建一个临时类型为“ cv1
T1
”并从初始化表达式复制初始化(dcl.init)。然后将引用绑定到临时。
最后,5.2.2.2适用。但是:
如果
T2
与T1
的参考相关:
- (5.2.2.3) cv1 应与 cv2 具有相同的cv资格,或更高的cv资格;和
- (5.2.2.4)如果引用是右值引用,则初始化表达式不应是左值。
T1
和T2
为T1
(T2
的返回类型的引用已被删除,仅用于确定值类别),因此他们可以使用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。