stackoverflow上有个问题:Why “universal references” have the same syntax as rvalue references? 就是说为什么这俩的形式都是T&&(T表示一个类型)。其中有一个回答很好,回答如下:
I think it happened the other way around. The initial idea was to introduce rvalue-references into the language, meaning that “the code providing the double-ampersand reference does not care about what will happen to the referred-to object”. This permits move semantics. This is nice.
Now. The standard forbids constructing a reference to a reference, but this was always possible. Consider:
template<typename T>
void my_func(T,T&){...}
...
my_func<int&>(a,b);
In this case the type of the second parameter should be int & &, but this is explicitly forbidden in the standard. So the references have to be collapsed, even in C++98. In C++98, there was only one kind of reference, so the collapsing rule was simple:
& & -> &
Now, we have two kinds of references, where && means “I don’t care about what may happen to the object”, and & meaning “I may care about what may happen to the object, so you better watch what you’re doing”. With this in mind, the collapsing rules flow naturally: C++ should collapse referecnces to && only if no one cares about what happens to the object:
& & -> &
& && -> &
&& & -> &
&& && -> &&
With these rules in place, I think it’s Scott Meyers who noticed that this subset of rules:
& && -> &
&& && -> &&
Shows that && is right-neutral with regards to reference collapsing, and, when type deduction occurs, the T&& construct can be used to match any type of reference, and coined the term “Universal reference” for these references. It is not something that has been invented by the Committee. It is only a side-effect of other rules, not a Committee design.
And the term has therefore been introduced to distinguish between REAL rvalue-references, when no type deduction occurs, which are guaranteed to be &&, and those type-deduced UNIVERSAL references, which are not guaranteed to remain && at template specialization time.
也就说,其实并没有什么universal引用,它只是C++引用折叠规则的一个副产物罢了。而且这个一般universal引用只能在模板中发挥作用,一旦一个非模板函数的参数类型声明成T&&的形式,那个T已经是固定的了,所以T&&就是固定的,也就只能表示右值引用。而在模板中,那个T是不确定的,如果T是一个左值引用类型,即T = S&,那么T&&就等价于S& &&,根据上面的引用折叠规则,& && -> &,那么S& &&类型就是S&,也就是左值引用,其他同理。
下面看一个例子:
void f(int&& a) {
std::cout << "rvalue" << std::endl;
}
void f(int& a) {
std::cout << "lvalue" << std::endl;
}
template<typename T>
void test(T&& a) {
f(forward<T>(a));
}
template<typename T>
T&& forward(std::remove_reference_t<T>& arg)
{
return static_cast<T&&>(arg);
}
int main()
{
int a = 10;
test(a);
test(std::move(a));
//system("pause"); //for windows VS to pause to see the output
return 0;
}
这里实现了一个非常非常简单的完美转发(perfect forwarding)。我们下面一步一步分析一下代码的运行过程:
//typename T 被推断成 int
void test(int&& a){
f(forward<int>(a));
}
//typename T 被推断成 int&
void test(int& a){ //int& && -> int&
f(forward<int&>(a));
}
//typename T 是int&,
int& forward(int& arg) //std::remove_reference_t<int&> 就是 int
{
return static_cast<int&>(arg); //int& && -> int&,返回类型也是一样的原理
}
//typename T 被推断成 int
void test(int&& a){
f(forward<int>(a));
}
//typename T 是int,
int&& forward(int& arg) //std::remove_reference_t<int> 就是 int
{
return static_cast<int&&>(arg);
}
这里简要说明一下为什么需要完美转发。因为在test函数里面,其参数a是一个左值,不管它前面的类型是右值类型还是左值类型,它都是一个左值。而如果用这个a调用f的话,只能调用f的左值重载版本,永远调用不到其右值重载版本。至于为什么a是一个左值,简单说就是只要有名字的变量都是左值,具体可以参考右值引用。而完美转发则可以实现传递进来什么类型,我就返回什么类型,所以就可以利用其实现对不同重载版本的f的调用。
下面我简单修改一下mian函数:
... //as before
int main()
{
int a = 10;
int &b = a;
int&& c = std::move(a);
test(b);//这里b是一个左值引用
test(c);//c是一个右值引用
//system("pause"); //for windows VS to pause to see the output
return 0;
}
这里,有两点想说明: