我仍然被那些为了支持移动和转发而发明的规则弄糊涂了。有一件事我还不太确定:
转发引用是否只是rvalue引用(应用了引用折叠规则)?
如果它是一个rvalue引用,那么为什么函数:
template<typename T>
void func(T&&);
不仅接受rvalues,还接受lvalues?
我不确定这个答案是否会让你满意,但我可以指出标准的相关部分。简而言之,引用t&&
在“语法上”始终是rvalue引用,但有时它最终声明的类型是lvalue引用类型。
当这种情况作为模板参数推演的结果发生时,作为一个方便的速记,整个构造被称为“转发引用”。(这种情况需要引用折叠,但模板参数推演并不是引用折叠发生的唯一时间。)
现在来看看标准的措辞。首先,我们有[dcl.ref](例如p2、p6):
使用&
声明的引用类型称为lvalue引用,使用&
声明的引用类型称为rvalue引用。[...]
如果typedef-name(9.1.3,13.1)或decltype-specifier(9.1.7.2)表示对t
类型的引用tr
类型的tr
类型,则尝试创建类型“lvalue reference to cvtr
”将创建类型“lvalue reference tot
”,而尝试创建类型“rvalue reference to cvtr
”将创建类型“tr
”将创建类型“tr
”。[注:此规则称为引用折叠。-结尾注]
最后,模板参数推演的情况在[temp.defint.call]P3中处理:
转发引用是对cv-unqualified模板参数[...]的rvalue引用
换句话说,转发引用是rvalue引用,但它也接受lvalue引用。(请注意,标准的“转发引用”定义实际上并不要求推导模板参数,尽管这是您通常希望触发引用折叠行为的主要方式。)
在t
被替换之前,t&&
是一个rvalue引用(显然)。
在T
被替换之后(以及在引用折叠之后),T&
或者保持rvalue引用(如果T
不是引用),或者成为lvalue引用(如果T
是lvalue引用)。