c++11-17 模板核心知识(十)—— 区分万能引用(universal references)和右值引用

引子

T&&在代码里并不老是右值引用:c++

void f(Widget&& param);      // rvalue reference

Widget&& var1 = Widget();      // rvalue reference

auto&& var2 = var1;        // not rvalue reference


template<typename T>
void f(std::vector<T>&& param);      // rvalue reference


template<typename T>
void f(T&& param);     // not rvalue reference

T&&表明两种含义:函数

  • 右值引用
  • 万能引用(universal references, or forwarding references)

如何区分

万能引用通常出如今两个场景中:设计

  • 模板参数
template<typename T>
void f(T&& param); // param is a universal reference
  • auto声明
auto&& var2 = var1; // var2 is a universal reference

咱们分别讨论下这两种场景。code

模板参数

咱们注意到,涉及到万能引用的地方,都会有参数推导的过程,例如上面的T和var2. 而右值引用则没有这个过程:get

void f(Widget&& param);        // no type deduction; param is an rvalue reference

Widget&& var1 = Widget();     // no type deduction; var1 is an rvalue reference

可是即便语句设计到参数推导,也不必定就是万能引用。例如:io

template<typename T>
void f(std::vector<T>&& param);       // param is an rvalue reference


std::vector<int> v;
f(v); // error! can't bind lvalue to rvalue reference

这点仍是比较好理解的。万能引用须要依靠表达式来初始化本身是右值引用仍是左值引用,可是上面这个例子没有表现出这一点,它仅仅是推断了T的类型,可是param的类型一直都是std::vector<T>&&模板

咱们再举一个vector中的例子:class

template<class T, class Allocator = allocator<T>> 
class vector { 
public:

void push_back(T&& x);      // rvalue reference


template <class... Args> 
void emplace_back(Args&&... args);      // universal reference
};
  • push_back(T&& x)中的T&&为右值引用,由于这个虽然是T&&,可是不涉及到参数推导。当push_back被instantiated时,实际的调用相似于:
std::vector<Widget> v;

...
class vector<Widget, allocator<Widget>> {
public:
void push_back(Widget&& x);       // rvalue reference
…
};

能够很明显的看出此时没有参数推导的过程。sed

  • template <class... Args> emplace_back(Args&&... args)中的Args&&为万能引用。Args与T是相互独立的,因此Args有一个独立的参数推断过程。

const disqualify universal reference

有意思的是,当参数加上const后,就必定是右值引用:lambda

template <class T> int f(T&& heisenreference);
template <class T> int g(const T&&);

int i;
int n1 = f(i);         // calls f<int&>(int&)
int n2 = f(0);     // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which would bind an rvalue reference to an lvalue

至于为何会有这个规定,按照Why adding const makes the universal reference as rvalue的说法,大致有两点缘由:

  • const T&&容许你重载一个函数模板,它只接受右值引用。若是const T&&也被当作universal reference,那么将没有办法让函数只接受右值引用。
  • 显示禁用某个函数接受右值引用:template <typename T> void cref(const T&&) = delete;

auto声明

对于auto的场景来讲,全部的auto&&都是万能引用,由于它老是有参数推导的过程。例如定义一个记录函数执行时间的lambda(C++14中容许使用auto来声明lambda的函数):

auto timeFuncInvocation = [](auto &&func, auto &&... params) {  
  start timer;
  std::forward<decltype(func)>(func)(                      // invoke func
      std::forward<decltype(params)>(params)...      // on params
  );
  stop timer and record elapsed time;
};

(完)

朋友们能够关注下个人公众号,得到最及时的更新:

相关文章
相关标签/搜索