-- TOC --
当引用指向引用的时候,这里的引用可以是lvalue也可以是rvalue,最终的类型会有部分被折叠(collapsing)到一起,具体规则如下(注意地址符之间的空格):
Before | After |
---|---|
A& & |
A& |
A& && |
A& |
A&& & |
A& |
A&& && |
A&& |
只有全部为右值引用的情况下才会变为右值引用。
如下定义的模板参数,被称为universal reference:
template<typename T>
void func(T &&t);
结合Reference Collapsing规则,当用lvalue
调用func时,T为左值类型(这是唯一的一个类型推导出T为左值类型的情况),当用rvalue
调用func时,T就为T
。
直觉:parameter pack也可以这样表达:
Args&&... args
既然是deduction,就不能指定类型!例如std::move的使用。
当一个右值引用传入函数时,它就有了名字,所以继续使用它,或者调用其他函数需要它时,根据C++标准的定义,这个参数变成了一个左值。那么,编译器永远不会调用接下来函数的右值版本,这在一些情况下,会造成拷贝。为了解决这个问题 C++11引入了完美转发,调用std::forward
返回的值,若原来是一个右值,那么返回的就是一个右值,否则为一个左值。这样的处理,完美地转发了原有参数的左右值属性,不会造成一些不必要的拷贝。
std::forward(a)
,是a原来定义时的类型。#include <iostream>
#include <string>
using namespace std;
int main(){
string a{"abcde"};
// ra类型是a的rvalue
string &&ra { std::move(a) };
// 直接使用ra,触发copy
string b { ra };
// 输出:abcde
cout << a << endl;
// 使用ra的原始类型,a的rvalue,触发move
string c { std::forward<string>(ra) }; //这是错误的用法,见下面!
// a已经是空值,无输出
cout << a << endl;
return 0;
}
另一个示例:
#include <iostream>
#include <string>
using namespace std;
template<typename T>
void print(T &t){
cout << "lvalue\n";
}
template<typename T>
void print(T &&t){ // Template Rvalue Argument Deduction
cout << "rvalue\n";
}
template<typename T>
void test(T &&t){
// directly to use t,always lvalue
print(t);
// depends t at the caller
print(forward<T>(t));
// always rvalue
print(move(t));
}
int main(){
test(1);
cout << "----\n";
int x = 1;
test(x);
cout << "----\n";
test(forward<int>(x));
return 0;
}
输出:
lvalue
rvalue
rvalue
----
lvalue
lvalue
rvalue
----
lvalue
rvalue
rvalue
注意一个细节:main中的test(forward<int>(x))
,x变成了rvalue!我理解,这是不正确的forward用法,forward中的参数,应该是模板参数。因此,此文中间那段测试代码,是错误的。
本文链接:https://cs.pynote.net/sf/c/cpp/202301181/
-- EOF --
-- MORE --