Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zhangyachen opened this issue Nov 30, 2020 · 0 comments
Labels

Comments

@zhangyachen
Copy link
Owner

引子

T&&在代码里并不总是右值引用:

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

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

模板参数

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

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

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

但是即使语句设计到参数推导,也不一定就是万能引用。例如:

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中的例子:

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
…
};

可以很明显的看出此时没有参数推导的过程。

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

const disqualify universal reference

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

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;
};

(完)

朋友们可以关注下我的公众号,获得最及时的更新:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant