我刚刚对那些(相当)新功能进行了一些研究,我想知道为什么C++委员会决定为它们引入相同的语法?似乎开发人员不必浪费一些时间来理解它是如何工作的,并且一个解决方案让我们考虑进一步的问题.在我的情况下,它从问题开始,可以简化为:
#includetemplate void f(T& a) { std::cout << "f(T& a) for lvalues\n"; } template void f(T&& a) { std::cout << "f(T&& a) for rvalues\n"; } int main() { int a; f(a); f(int()); return 0; }
我首先在VS2013上编译它,它按照我的预期工作,结果如下:
f(T& a) for lvalues f(T&& a) for rvalues
但有一个可疑的事情:intellisense强调了f(a).我做了一些研究,我明白这是因为类型崩溃(Scott Meyers将它命名为通用引用),所以我想知道g ++对它的看法.当然它没有编译.微软实现他们的编译器以更直观的方式工作是非常好的,但我不确定它是否符合标准,是否应该在IDE中存在这种差异(编译器与智能感知,但实际上可能存在在某种程度上是有道理的).好的,回到问题所在.我用这种方式解决了它:
templatevoid f(T& a) { std::cout << "f(T& a) for lvalues\n"; } template void f(const T&& a) { std::cout << "f(T&& a) for rvalues\n"; }
现在没有任何类型崩溃,只是(r/l)值的正常重载.它在g ++上编译,intellisense停止抱怨,我几乎满意.差不多,因为我想过如果我想要改变对象状态中通过右值引用传递的东西怎么办?我可以在必要的时候描述一些情况,但是这个描述太长了,无法在这里提出.我用这种方式解决了它:
templatevoid f(T&& a, std::true_type) { std::cout << "f(T&& a) for rvalues\n"; } template void f(T&& a, std::false_type) { std::cout << "f(T&& a) for lvalues\n"; } template void f(T&& a) { f(std::forward (a), std::is_rvalue_reference ()); }
现在它编译所有经过测试的编译器,它允许我在rvalue参考实现中更改对象状态,但它看起来不太好,这是因为通用引用和右值引用的语法相同.所以我的问题是:为什么C++委员会没有为通用引用引入另一种语法?我认为这个功能应该通过T?,auto?或类似信号进行信号处理,但不能作为T &&和auto &&与rvalue引用冲突.使用这种方法,我的第一个实现是完全正确的,不仅适用于MS编译器.谁能解释委员会的决定?
您回答了自己的问题:"通用引用"只是引用折叠的右值引用案例的名称.如果参考折叠需要另一种语法,则不会再引用折叠.参考折叠只是将引用限定符应用于引用类型.
所以我想知道g ++对它的看法.当然它没有编译.
你的第一个例子是格式良好的.GCC 4.9无需投诉即可编译,输出与MSVC一致.
差不多,因为我想过如果我想改变对象状态中通过右值引用传递的东西怎么办?
Rvalue引用不适用const
语义; 您始终可以更改传递的对象的状态move
.可变性对他们的目的是必要的.虽然有这样的事情const &&
,但你永远不需要它.
其他人已经提到过,参考折叠规则是普遍参考工作的关键,但还有另一个(可以说是)同样重要的方面:当模板参数是形式时,模板参数推导T&&
.
实际上,关于这个问题:
为什么"通用引用"与rvalue引用具有相同的语法?
在我看来,模板参数的形式更重要,因为这完全是关于语法.在C++ 03中,模板函数无法知道传递对象的值类别(rvalue或lvalue).C++ 11更改了模板参数推导以解释:14.8.2.1 [temp.deduct.call]/p3
[...]如果
P
是对cv-unqualified模板参数的rvalue引用,并且参数是左值,则使用类型"左值引用A
"代替A
类型推导.
这比最初提出的措辞(由n1770给出)复杂一点:
如果
P
是以下形式的右值参照型的CVT&&
,其中T
是一个模板类型参数,所述参数是一个左值,推导的模板参数值T
是A&
.[例:template<typename T> int f(T&&); int i; int j = f(i); // calls f<int&>(i)---结束例子]
更详细地,上面的调用触发实例化f<int&>(int& &&)
,在应用参考折叠之后,实例化变为f<int&>(int&)
.另一方面f(0)
实例化f<int>(int&&)
.(注意有没有&
内< ... >
.)
声明没有其他形式会推断T
到int&
,并触发的实例f<int&>( ... )
.(请注意,a &
之间可以出现( ... )
但不能出现< ... >
.)
总之,当执行类型推导时,句法形式T&&
允许原始对象的值类别在函数模板体内可用.
与此事实相关,请注意必须使用std::forward<T>(arg)
而不是std::forward(arg)
完全因为T
(不arg
)记录原始对象的值类别.(作为一种谨慎的说法,std::forward
"人为" 的定义迫使后者的编译无法阻止程序员犯这个错误.)
回到最初的问题:"为什么委员会决定使用表格T&&
而不是选择新的语法?"
我无法说出真正的原因,但我可以推测.首先,它向后兼容C++ 03.其次,最重要的是,这是一个非常简单的解决方案,可以在标准中说明(一个段落更改)并由编译器实现.拜托,别误会我的意思.我不是说委员会成员是懒惰的(他们当然不是).我只是说他们将附带损害的风险降到最低.
我认为它发生了反过来.最初的想法是将rvalue-references引入语言,这意味着"提供双&符号引用的代码并不关心引用对象会发生什么".这允许移动语义.这很好.
现在.标准禁止构建对引用的引用,但这始终是可行的.考虑:
template<typename T> void my_func(T, T&) { /* ... */ } // ... my_func<int&>(a, b);
在这种情况下,第二个参数的类型应该是int & &
,但在标准中明确禁止.因此,即使在C++ 98中,也必须折叠引用.在C++ 98中,只有一种引用,因此折叠规则很简单:
& & -> &
现在,我们有两种参考,&&
意思是"我不关心对象会发生什么",&
意思是"我可能会关心对象会发生什么,所以你最好看看你在做什么" .考虑到这一点,崩溃规则自然而然地流动:&&
只有当没有人关心对象发生什么时,C++ 才会崩溃引用:
& & -> & & && -> & && & -> & && && -> &&
有了这些规则,我认为是Scott Meyers注意到这个规则的子集:
& && -> & && && -> &&
显示&&
关于参考折叠的右中性,并且当发生类型推导时,该T&&
构造可用于匹配任何类型的引用,并为这些引用创造术语"通用引用".这不是委员会发明的东西.这只是其他规则的副作用,而不是委员会的设计.
因此,引入该术语是为了区分REAL rvalue-references,当没有发生类型推导时,保证是哪种&&
类型推导,以及那些不保证保留&&
在模板特化时间的类型推导的UNIVERSAL引用.