1 回答
概述
众所周知,从 C++11 开始,类型参数T&&
称为右值引用[ ISO/IEC 14882:2011 §8.3.2/p2 References [dcl.ref] ]。也就是说,除非T
是模板参数类型,auto
或者是typedef
某个左值引用类型。
例子:
template<typename T> void foo(T&& p) { // -> T is a template parameter ... } auto &&p = expression;
尽管从技术上讲T&&
,上述示例中的引用仍然是右值引用,但它的行为与常规引用有很大不同。
自然,您会问“为什么这种特殊情况没有特殊语法”。答案是&&
C++ 委员会有意为这个特殊结构重载了语法。然而,他们错过了命名这种特殊情况。
在没有为这个特定结构命名的情况下,Scott Meyers创造了广为人知的术语/名称通用引用。
然而,由于多种原因,委员会决定这个名称不合适。因此,Herb Sutter、Bjarne Stroustrup和Gabriel Dos Reis提出的N4164提案提议将名称更改为Forwarding References。
Forwarding References这个名字在委员会成员的非正式讨论中得到了最多的支持,包括前面提到的提案的作者。有趣的是,斯科特·迈耶斯本人在他最初的“通用参考”演讲中引入了这个术语。然而,后来他决定沿用通用引用这个名称。因为这个决定发挥了作用,当时他认为术语转发引用也不包括这种auto&&
情况。
为什么不是通用参考?
根据提案,Universal references一词虽然是一个合理的名称,含义明显,但它在几个方面恰好是错误的。
通用参考必须意味着以下内容:
- 可以在任何地方使用的参考;或者
- 可用于一切的参考;或者
- 相似的东西。
显然,情况并非如此,也不是该构造的适当使用。此外,这个名称会鼓励许多人认为具有这样名称的东西是为了“普遍”使用。委员会认为这是一件坏事。
此外,“通用引用”本身甚至不是真正的引用,而是一组规则,用于在特定上下文中以特定方式使用引用,并具有对该使用的某种语言支持,并且该使用是 forwarding。
为什么auto&&
也被视为转发案例
auto&&
也被认为是一个前向案例,因为它遵循参考折叠规则。例如在:
- 形式的通用 lambda,
[](auto&& x){ … }
for
-形式的范围循环,for(auto &&i : v) { ... }
- 最后,一般来说,
auto&&
局部变量是用于转发的。
转发参考的标准措辞
术语转发引用在标准草案 N4527中的以下位置提到:
§14.8.2.1/从函数调用[temp.deduct.call] (Emphasis Mine)推导出模板参数:
如果 P 是 cv 限定类型,则 P 类型的顶级 cv 限定符将被忽略以进行类型推导。如果 P 是引用类型,则使用 P 所引用的类型进行类型推导。转发引用是对 cv 非限定模板参数的右值引用。如果 P 是转发引用并且参数是左值,则使用类型“对 A 的左值引用”代替 A 进行类型推导。[ 例子:
template <class T> int f(T&& heisenreference); template <class T> int g(const T&&); int i; int n1 = f(i); // calls f<int&>(int&) int n2 = f(0); // calls f<int>(int&&) int n3 = g(i); // error: would call g<int>(const int&&), which // would bind an rvalue reference to an lvalue
—结束示例]
§14.8.2.5/p10 从类型 [temp.deduct.type] 推导出模板参数:
类似地,如果 P 具有包含 (T) 的形式,则将 P 的相应参数类型列表的每个参数类型 Pi 与 A 的相应参数类型列表的相应参数类型 Ai 进行比较。如果 P 和 A 是当获取函数模板的地址 (14.8.2.2) 或从函数声明 (14.8.2.6) 推导模板参数时源自推导的函数类型,并且 Pi 和 Ai 是顶级参数类型列表的参数P 和 A 分别是,如果 Pi 是转发引用(14.8.2.1),Ai 是左值引用,则调整 Pi 的类型,此时将 Pi 的类型更改为模板参数类型(即 T&& 更改为简单T)。[注:结果,当 Pi 是
T&&
,Ai 是X&
,调整后的 Pi 将为 T,导致 T 推导出为X&
。— 尾注] [示例:template <class T> void f(T&&); template <> void f(int&) { } // #1 template <> void f(int&&) { } // #2 void g(int i) { f(i); // calls f<int&>(int&), i.e., #1 f(0); // calls f<int>(int&&), i.e., #2 }
— end example ] 如果 Pi 对应的参数声明是函数参数包,则将其声明符的类型与 A 的参数类型列表中的每个剩余参数类型进行比较。每次比较都推导出模板参数的后续位置由函数参数包扩展的模板参数包。在偏序(14.8.2.4)期间,如果 Ai 最初是一个函数参数包: