此部分内容和右值引用重复
2.引用折叠
引用折叠规则:
X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&&
函数模板参数推导规则(右值引用参数部分):
当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
若实参为左值(U,U&,U&&的有名左值 等)则认为其类型为U& ,则模板参数 T 应推导为引用类型 U& 。
(根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
若实参为右值(U(),move(左值) 亡值,static_cast左值 亡值等)则认为其类型为U&& ,则模板参数 T 应推导为非引用类型 U 。
(根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U,这里强制规定T ≡ U )
std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。
std::remove_reference::type ≡ U
std::remove_reference::type ≡ U
std::remove_reference::type ≡ U
以下语法形式将把表达式 t 转换为T类型的亡值(右值的一种)staticcast
非引用类型的转型表达式,如static_cast
static_cast
static_cast
staticcast<__新类型__> 如果新类型是对象类型的右值引用则返回亡值(右值的一种),新类型是左值引用(或者是某个函数类型的右值引用很奇怪也很少见)则返回左值,除去这两种情况其他新类型cast_返回的都是纯右值。
对于对象的引用必须是左值(常量引用除外)const引用能够绑定到临时对象, 并将临时对象的生命周期由”创建临时对象的完整表达式”提升至”绑定到的const引用超出作用域”。
例
template<typename T>
void f(T&& param);//T&&为万能引用
...
int x;
...
f(10); // invoke f on rvalue
f(x); // invoke f on lvalue
因为万能引用的特性,如果用来初始化万能引用的表达式是个左值,那万能引用就变为左值引用。如果用来初始化万能引用的表达式是个右值,那么万能引用就变为右值引用。
10是字面右值,万能引用自动推导为void f(int&& param);—-T被推导为int // f instantiated from rvalue
类型T的 rvalues 被推导为 T—-10是int型的rvalues,则万能引用会将T推导为int
ok没问题
x是左值,c++11以前 万能引用自动推导为
void f(int& && param); // initial instantiation of f with lvalue
类型T的lvalues被推导为T&—x是int型的lvalues,则万能引用会将T推导为int&
c++11以前不行,在c++11以后推出引用折叠解决了这个问题
c++11 f(x);万能引用自动推导为 void f(int& param);
引用折叠只有两条规则(就是上面那张图)
一个右值引用的右值引用会变成 (“折叠为”) 一个 rvalue reference.
所有其他种类的”引用的引用” (i.e., 组合当中含有lvalue reference) 都会折叠为 lvalue reference。引用折叠的解释 也是下面这个例子
对于 template
如果传递过去的实参是左值(不管是类型是左值引用,or 普通左值,or 右值引用类型的左值),T 的推导结果(万能引用的推导结果)是左值引用(比如传入实参是X&,X&&类型的有名左值,X普通非引用类型 —则T都被推导为 其中类型 X的左值引用 X&),那 T&& 的结果仍然是左值引用——即 X& && 坍缩成了X&
如果传递过去的实参是右值(纯右值临时对象or move后的左值—亡值 or static_cast<右值引用>—亡值 ),T 的推导结果(万能引用的推导结果)是参数的类型本身(是实参类型本身 比如传入的实参是X型的右值 or move(左值) or static_cast
int x;
...
int&& r1 = 10; // r1’s type is int&&
int& r2 = x; // r2’s type is int&
当一个变量本身的类型是引用类型时会出一些问题
3.c++中遇到模板类型需要推导的情况
例1
template<typename T>
void f(T &¶m) {
static_assert(std::is_lvalue_reference<T>::value, "T& is lvalue reference");
cout << "T& is lvalue reference" << endl;
}
int main() {
int x;
int &&r1 = 10;
int &r2 = x;
f(r1);
f(r2);
}
r1 和r2 的类型都被推导为 int&
因为r1和r2都是有名的,都是左值,传入万能引用中进行推导得到的就是左值引用类型的param
例2 auto场景
Widget&& var1 = someWidget; // var1 is of type Widget&& (no use of auto here)
auto&&是万引用 传入左值var1,发生自动推导和引用折叠。auto&&被推导为左值引用Widget&
auto&& var2 = var1; // var2 is of type Widget& (see below)
例3 typedef场景
template<typename T>
class Widget {
typedef T& LvalueRefType;
typedef T&& RvalueRefType;
...
};
int main() {
Widget<int&> w;
}
注意万能引用是万能引用,引用折叠是引用折叠。并不是说引用折叠是万能引用专属的特性。上面这个例子就只发生了引用折叠
除了右值引用的右值引用会折叠为右值引用,所有其他种类的”引用的引用” (i.e., 组合当中含有lvalue reference) 都会折叠为 lvalue reference。
推导LvalueRefType,因为传入的T类型实例化为int&,这里T&出现的就是左值引用的左值引用(int& &),这里出现的T&&就是左值引用的右值引用(int& &&) 根据规则都会发生引用折叠变为左值引用int&。—-T==int&(就是这么实例化的) T&== LvalueRefType==int&(因为左值引用的左值引用 发生了引用折叠 变回了左值引用) T&&==RvalueRefType==int&(因为左值引用的右值引用 发生了引用折叠 变回了左值引用)
如果我们只用这些typedef,一样会发生引用折叠
void f(Widget
对typedef扩展void f(int& && param);—-左值引用的右值引用发生引用折叠变为
void f(int& param);
void f(Widget
void f(int& param);
例4 decltype场景
和模板和 auto 一样,decltype 对表达式进行类型推导时候可能会返回 T 或者 T&,然后decltype 会应用 C++11 的引用折叠规则。
decltype 的类型推导规则其实和模板或者 auto 的类型推导不一样。这里的细节过于晦涩
在这里先注意一个点
decltype 对一个具名的、非引用类型的变量,会推导为类型 T ,在相同条件下,模板和 auto 却会推导出 T&。
decltype((变量))双层括号的结果永远是引用类型,而decltype(变量)的结果只有当变量本身是一个引用时才会返回引用类型
decltype 进行类型推导只依赖于 decltype括号内的表达式; 用来对变量进行初始化的表达式的类型(如果有的话)会被忽略。—-即不看用于初始化的实参,只着眼于decltype括号内的表达式
如下例
Widget w1, w2;
auto&& v1 = w1;
auto&&是万能引用,传入w1是左值 推导T为Widget &,Widget& &&发生引用折叠变为Widget&。
decltype(w1)&& v2 = w2;
w1为具名非引用变量,推导为T为类型Widget ,Widget &&正常 是个右值引用,decltype推导类型并不会关心由于初始化v2变量的实参w2的类型,只关心decltype括号里的内容—-因为右值引用只能绑定右值,而这里给了个左值所以编译出错
注意C++的类型推导(auto decltype typedef 万能引用模板 引用折叠)是在编译时进行,编译完就明确知道调用的函数具体的所有细节
4.完美转发
void foo(const shape&)
{
puts("foo(const shape&)");
}
void foo(shape&&)
{
puts("foo(shape&&)");
}
void bar(const shape& s)
{
puts("bar(const shape&)");
foo(s);
}
void bar(shape&& s)
{
puts("bar(shape&&)");
foo(s);
}
int main()
{
bar(circle());
}
输出为:bar(shape&&)foo(const shape&)
bar(circle()); 首先circle()是个右值所以会调用右值版本的bar,
在bar中
void bar(shape&& s)
{
puts("bar(shape&&)");
foo(s);//传入的实参是个右值付给形参s(有名是左值), 一旦赋值完成右值就变为有名 不再是右值了,因为s是左值 所以调用的是void foo(const shape&)
}
输出为:
bar(shape&&)
foo(const shape&)
形参为匿名对象赋名导致其成为左值 就是不完美转交的根本原因。
//如果我们想bar中调用foo的右值引用版本,需要将s通过move强制变为右值
foo(std::move(s));
//或者通过static_cast强制将s类型变为右值引用类型
foo(static_cast<shape&&>(s));
但实际上恨过标准库中的函数是不知道参数类型的,但是依然保持了参数的值类别,左值依然是左值,右值依然是右值。 通过forward实现完美转发
template <typename T>
void bar(T&& s)
{
foo(std::forward<T>(s));
//通过forward保证s传入是左/右值保持为左/右值 通过完美转发保证调用正确的函数版本
}
circle temp;
bar(temp);//左值 传入左值类型s foo(const shape&)
bar(circle());//右值 foo(shape&&)
注意我们在调用bar时并没有指定模板参数bar
在 T 是模板参数时,T&& 的作用主要是保持值类别进行转发,它有个名字就叫“转发引用”(forwarding reference)。因为既可以用左值引用,也可以是右值引用,它也曾经被叫做“万能引用”(universal reference)。