c/c++语言开发共享模板参数的“右值引用”是转发引用

在C++11中, 不再只有逻辑与的含义,还可能是右值引用: 但也不尽然, 还可能是转发引用: “转发引用”(forwarding reference)旧称“通用引用”(universal reference),它的“通用”之处在于你可以拿一个左值绑定给转发引用,但不能给右值引用: 一个函数的参数要想 …

在c++11中,&&不再只有逻辑与的含义,还可能是右值引用:

void f(int&& i); 

但也不尽然,&&还可能是转发引用:

template<typename t> void g(t&& obj); 

“转发引用”(forwarding reference)旧称“通用引用”(universal reference),它的“通用”之处在于你可以拿一个左值绑定给转发引用,但不能给右值引用:

void f(int&& i) { }  template<typename t> void g(t&& obj) { }  int main() {     int n = 2;     f(1); //  f(n); // error     g(1);     g(n); } 

一个函数的参数要想成为转发引用,必须满足:

  • 参数类型为t&&,没有constvolatile

  • t必须是该函数的模板参数。

换言之,以下函数的参数都不是转发引用:

template<typename t> void f(const t&&); template<typename t> void g(typename std::remove_reference<t>&&); template<typename t> class a {     template<typename u>     void h(t&&, const u&); }; 

另一种情况是auto&&变量也可以成为转发引用:

auto&& vec = foo(); 

所以写范围for循环的最好方法是用auto&&

std::vector<int> vec; for (auto&& i : vec) {     // ... } 

有一个例外,当auto&&右边是初始化列表,如auto&& l = {1, 2, 3};时,该变量为std::initializer_list<int>&&类型。

转发引用,是用来转发的。只有当你的意图是转发参数时,才写转发引用t&&,否则最好把const t&t&&写成重载(如果需要的话还可以写t&,还有不常用的const t&&;其中t是具体类型而非模板参数)。

转发一个转发引用需要用std::forward,定义在<utility>中:

#include <utility>  template<typename... args> void f(args&&... args) { }  template<typename t> void g(t&& obj) {     f(std::forward<t>(obj)); }  template<typename... args> void h(args&&... args) {     f(std::forward<args>(args)...); } 

调用g有几种可能的参数:

  • int i = 1; g(i);tint&,调用g(int&)

  • const int j = 2; g(j);tconst int&,调用g(const int&)

  • int k = 3; g(std::move(k));g(4);tint(不是int&&哦!),调用g(int&&)

你也许会疑惑,为什么std::move不需要<t>std::forward需要呢?这得从std::forward的签名说起:

template<typename t> constexpr t&& forward(std::remove_reference_t<t>&) noexcept; template<typename t> constexpr t&& forward(std::remove_reference_t<t>&&) noexcept; 

调用std::forward时,编译器无法根据std::remove_reference_t<t>反推出t,从而实例化函数模板,因此<t>需要手动指明。

但是这并没有从根本上回答问题,或者可以进一步引出新的问题——为什么std::forward的参数不定义成t&&呢?

原因很简单,t&&会把t&const t&t&&const t&&(以及对应的volatile)都吃掉,有了t&&以后,再写t&也没用。

且慢,t&&参数在传入函数是会匹配到t&&吗?

#include <iostream> #include <utility>  void foo(int&) {     std::cout << "int&" << std::endl; }  void foo(const int&) {     std::cout << "const int&" << std::endl; }  void foo(int&&) {     std::cout << "int&&" << std::endl; }  void bar(int&& i) {     foo(i); }  int main() {     int i;     bar(std::move(i)); } 

不会!程序输出int&。在函数bar中,i是一个左值,其类型为int的右值引用。更直接一点,它有免费精选名字大全,所以它是左值。

因此,如果std::forward没有手动指定的模板参数,它将不能区分t&t&&——那将是“糟糕转发”,而不是“完美转发”了。

最后分析一下std::forward的实现,以下代码来自libstdc++:

template<typename _tp>   constexpr _tp&&   forward(typename std::remove_reference<_tp>::type& __t) noexcept   { return static_cast<_tp&&>(__t); }  template<typename _tp>   constexpr _tp&&   forward(typename std::remove_reference<_tp>::type&& __t) noexcept   {     static_assert(!std::is_lvalue_reference<_tp>::value, "template argument"                   " substituting _tp is an lvalue reference type");     return static_cast<_tp&&>(__t);   } 
  • 当转发引用t&& obj绑定左值int&时,匹配第一个重载,_tptint&,返回类型_tp&&int&(引用折叠:& && &&&& &都折叠为&,只有&& &&折叠为&&);

  • const int&同理;

  • 当转发引用绑定右值int&&时,匹配第二个重载,_tpint,返回类型为int&&

  • const int&&同理。

综上,std::forward能完美转发。

程序员总是要在stack overflow上才能学会一点东西。

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/599756.html

(0)
上一篇 2021年5月9日
下一篇 2021年5月9日

精彩推荐