考虑如下代码:
void f(Widget &&parm); // 右值引用
Widget &&var1 = Widget(); // 右值引用
auto &&var2 = var1; // 非右值引用
template<typename T>
void f(std::vector<T> &¶m); // 非右值引用
template<typename T>
void f(T &¶m); // 非右值引用
T&&
有两种不同的含义:
- 其中一种含义为右值引用。
- 另外一种含义,则表示既可以是右值引用,亦可是左值引用。带有这种含义的引用在代码中形如右指值引用(
T&&
),称为万能引用;万能引用在以下两种情形出现:
template<typename T>
void f(T &¶m); // param是个万能引用
auto &&var2 = var1; // var2是个万能引用
上述代码的共同之处,在于它们都涉及类型推导。param
和var2
都是推导得到的;如果你看到了T&&
,但是没有涉及类型的推导,那么,这就是一个右值引用,如:
void f(Widget &&parm); // 右值引用
Widget &&var1 = Widget(); // 右值引用
因为万能引用首先是一个引用,所以必须进行初始化;万能引用的初始化物会决定它代表是个左值引用还是右值引用:
template<typename T>
void f(T &¶m); // param为万能引用
Widget w;
f(w); // 左值被传递给f,param的类型是Widget&,左值引用
f(std::move(w)); // 右值被传递给f,param的类型是Widget&&,右值引用
若要使得一个引用称为万能引用,其涉及到类型推导是必要条件,而不是充分条件:
template<typename T>
void f(std::vector<T> && param); // param是个右值引用
当f
被调用时,类型T將被推导,但是param
的类型声明形式不是T&&
,而是std::vector<T>&&
;这个事实在你试图传递左值给f
时编译器很乐意为你确认出来:
std::vector<int> v;
f(v); // 错误!不能给右值绑定一个左值
如果你在一个模板内看到一个函数形参写作T&&
,你可能会想当然认为它肯定是个万能引用。不能想当然,如下:
template<class T, class Allocator = allocator<T>>
class vector {
public:
void push_back(T&& x);
};
push_back
不涉及类型推导,因为push_back
作为vector
的一部分,如果不存在特定的vector
实例,则它也无从存在。该实例的类型完全决定了push_back
的声明类型,即:std::vector<Widget> v
会导致std::vector
模板具现化为如下实例:
template<class T, class Allocator = allocator<T>>
class vector {
public:
void push_back(Widget&& x); //右值引用
};
作为对比,emplace_back
却实实在在的涉及类型推导:
template<class T, class Allocator = allocator<T>>
class vector {
public:
template<class... Args>
void emplace_back(Args&&... args);
};
其中的类型形参Args
独立于vector
的类型形参T,所以,Args
必须在每次emplace_back
被调用时进行推导 。