2019独角兽企业重金招聘Python工程师标准>>>
背景
启用C++11编译老代码的时候,g++报错。示例代码如下:
#include
int main() {
int a = 0;
auto b = std::make_pair(a, 1);
return 0;
}
出错信息:
test.cpp: In function ‘int main()’:
test.cpp:5:41: error: no matching function for call to ‘make_pair(int&, int)’
auto b = std::make_pair(a, 1);
^
test.cpp:5:41: note: candidate is:
In file included from /usr/include/c++/4.8/utility:70:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/stl_pair.h:276:5: note: template constexpr std::pair::__type, typename std::__decay_and_strip<_T2>::__type> std::make_pair(_T1&&, _T2&&)
make_pair(_T1&& __x, _T2&& __y)
^
/usr/include/c++/4.8/bits/stl_pair.h:276:5: note: template argument deduction/substitution failed:
test.cpp:5:41: note: cannot convert ‘a’ (type ‘int’) to type ‘int&&’
auto b = std::make_pair(a, 1);
^
为何启用C++11后编译出错了?
从出错信息里已经可以看到C++11版本std::make_pair的原型为:
template
std::make_pair(_T1&&, _T2&&);
由于代码里直接指定了类型,于是std::make_pair实例化为:
std::make_pair(int&&, int&&);
即std::make_pair接受两个int类型的右值,由于变量a不是右值,于是报错。就比如下面这条语句不能编译通过一样:
int &&b = a; // error: cannot bind ‘int’ lvalue to ‘int&&’
事实上,示例代码里并不是std::make_pair的正确用法,正确的用法是无需指定类型,让编译器自动推导,即:
auto b = std::make_pair(a, 1);
那为什么这样就能编译通过呢?
右值引用的两个规则
模板参数的推导规则
若模板函数的参数类型为右值引用,如:
template
void foo(T&&);
那么T的推导规则为:当foo的实参是A类型的左值时,T的类型是A&;当foo的是实参是A类型的右值时,T的类型是A。
引用折叠规则
根据上一条规则,当foo的实参为A类型的左值时,foo将变成:
void foo(A& &&);
出现了引用的引用。在C++里,引用的引用是不存在的。为了解决这个问题,C++11引入了引用折叠规则,对于由于类型推导而出现的引用的引用,根据以下规则折叠:
A& & => A&
A& && => A&
A&& & => A&
A&& && => A&&
所以当foo的实参为A类型的左值时,foo事实上会变成:
void foo(A&);
而对于std::make_pair的例子,当不显式指定类型时,根据以上两个规则,std::make_pair将被实例化成:
std::make_pair(int&, int);
和实参类型匹配,于是编译通过。
右值引用看似简单,其实是有很多点需要特别注意的。Scott Meyers都认为这是C++11里面最重要的特性,并且容易搞混。
参考文章
C++ Rvalue References Explained, by Thomas Becker[这篇文章的译文在这里]
Universal References in C++11, by Scott Meyers
A Brief Introduction to Rvalue References, by Howard E. Hinnant, Bjarne Stroustrup, and Bronek Kozicki