C++11 模板参数的“右值引用”是转发引用吗

在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&&,没有const或volatile;
  • 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>中:


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

  • int i = 1; g(i);,T为int&,调用g(int&);
  • const int j = 2; g(j);,T为const int&,调用g(const int&);
  • int k = 3; g(std::move(k));或g(4);,T为int(不是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&时,匹配第一个重载,_Tp即T为int&,返回类型_Tp&&为int&(引用折叠:& &、& &&、&& &都折叠为&,只有&& &&折叠为&&);
  • const int&同理;
  • 当转发引用绑定右值int&&时,匹配第二个重载,_Tp为int,返回类型为int&&;
  • const int&&同理。

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

程序员总是要在Stack Overflow上撞撞墙才能学会一点东西。

到此这篇关于C++11 模板参数的“右值引用”是转发引用吗的文章就介绍到这了,更多相关C++11 右值引用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中引用传递与指针传递的区别(面试常见)

    最近Garena面试的过程中,面试官提了一个问题,C++中引用传递和指针传递的区别? 根据自己的经验,联想到了swap函数,只知道既可以用引用来实现,又可以用指针传递来实现,至于二者有何区别,自己还真没有考虑过. 痛定思痛,受虐之后,赶紧弥补自己的知识漏洞. 通过在网上搜集资料,自己也整理了一下. 精简版: 指针:变量,独立,可变,可空,替身,无类型检查: 引用:别名,依赖,不变,非空,本体,有类型检查: 完整版: 1. 概念 指针从本质上讲是一个变量,变量的值是另一个变量的地址,指针在逻辑上是

  • 详解C++ 引用

    引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字.一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量. C++ 引用 vs 指针 引用很容易与指针混淆,它们之间有三个主要的不同: 不存在空引用.引用必须连接到一块合法的内存. 一旦引用被初始化为一个对象,就不能被指向到另一个对象.指针可以在任何时候指向到另一个对象. 引用必须在创建时被初始化.指针可以在任何时间被初始化. C++ 中创建引用 试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位

  • 详解C++中指针和引用的区别

    1.指针和引用的本质(是什么) (1)指针是存放内存地址的一种变量,特殊的地方就在它存放的是内存地址.因此,指针的大小不会像其他变量一样变化,只跟当前平台相关--不同平台内存地址的范围是不一样的,32位平台下,内存最大为4GB,因此只需要32bit就可以存下,所以sizeof(pointer)的大小是4字节.64位平台下,32位就不够用了,要想内存地址能够都一一表示,就需要64bit(但是目前应该没有这么大的内存吧?),因此sizeof(pointer)是8. (2)引用的本质是"变量的别名&q

  • C++11右值引用和转发型引用教程详解

    右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如今被称作左值引用 lvalue reference)多一个&. 如果把经由T&&这一语法形式所产生的引用类型都叫做右值引用,那么这种广义的右值引用又可分为以下三种类型: 无名右值引用 具名右值引用 转发型引用 无名右值引用和具名右值引用的引入主要是为了解决移动语义问题. 转发型引用的引

  • C++ 中引用与指针的区别实例详解

    C++ 中引用与指针的区别实例详解 引用是从C++才引入的,在C中不存在.为了搞清楚引用的概念,得先搞明白变量的定义及引用与变量的区别,变量的要素一共有两个:名称与空间. 引用不是变量,它仅仅是变量的别名,没有自己独立的空间,它只符合变量的"名称"这个要素,而"空间"这个要素并不满足.换句话说,引用需要与它所引用的变量共享同一个内存空间,对引用所做的改变实际上是对所引用的变量做出修改.并且引用在定义的时候就必须被初始化.     参数传递的类型及相关要点: 1 按值

  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题. // // main.cpp // test // // Created by 杜国超 on 17/9/9. // Copyright © 2017年 杜国超. All rights reserved. // #include <iostream> #include <memory> #include <vector>

  • C/C++ 数组和指针及引用的区别

    C/C++ 数组和指针及引用的区别 1.数组和指针的区别 (1)定义 数组是一个符号,不是变量,因而没有自己对应的存储空间.但是,指针是一个变量,里面存储的内容是另外一个变量的地址,因为是变量所以指针有自己的内存空间,只不过里面存储的内容比较特殊. (2)区别 a.对于声明和定义,指针和数组是不相同的,定义为数组,则声明也应该是数组,不可混淆 b.当作下标操作符时,指针和数组是等价的.a[i]会被编译器翻译成*(a+i). c.当数组声明被用作函数形参的时候,数组实际会被当作指针来使用. (3)

  • C++ lambda 捕获模式与右值引用的使用

    lambda 表达式和右值引用是 C++11 的两个非常有用的特性. lambda 表达式实际上会由编译器创建一个 std::function 对象,以值的方式捕获的变量则会由编译器复制一份,在 std::function 对象中创建一个对应的类型相同的 const 成员变量,如下面的这段代码: int main(){ std::string str = "test"; printf("String address %p in main, str %s\n", &a

  • C++11 模板参数的“右值引用”是转发引用吗

    在C++11中,&&不再只有逻辑与的含义,还可能是右值引用: void f(int&& i); 但也不尽然,&&还可能是转发引用: template<typename T> void g(T&& obj); "转发引用"(forwarding reference)旧称"通用引用"(universal reference),它的"通用"之处在于你可以拿一个左值绑定给转发引用

  • 深入了解c++11 移动语义与右值引用

    1.移动语义 C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能.参考如下程序: //moveobj.cpp #include <iostream> #include <vector> using namespace std; class Obj { public: Obj(){cout <<"create obj" << e

  • C++11右值引用和移动语义的实例解析

    目录 基本概念 左值 vs 右值 左值引用 vs 右值引用 右值引用使用场景和意义 左值引用的使用场景 左值引用的短板 右值引用和移动语义 右值引用引用左值 右值引用的其他使用场景 完美转发 万能引用 完美转发保持值的属性 完美转发的使用场景 总结 基本概念 左值 vs 右值 什么是左值? 左值是一个表示数据的表达式,如变量名或解引用的指针. 左值可以被取地址,也可以被修改(const修饰的左值除外). 左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边. int main() { //以

  • 一文搞懂C++11万能引用和右值引用

    目录 前言 正文 万能引用 结语 参考: 前言 我们通过一个问题来进入今天的话题:1.形如 “type&&” 的结构,就是右值引用吗?2.以下哪些属于右值引用? ① void fun(Widget && param);② Widget && var1= Widget();③ auto && var2 = var1;④ template<typename T> void f(std::vector<T>&&

  • C++标准之(ravalue reference) 右值引用介绍

    1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了CopyElision.RVO(包括NRVO)等编译器优化技术,它们可以防止某些情况下临时对象产生和拷贝.下面简单地介绍一下CopyElision.RVO,对此不感兴趣的可以直接跳过: (1)CopyElision CopyElision技术是为了防止某些不必要的临时对象产生和拷贝,例如: 复制代码 代码如下: structA{ A(

  • 深入解读C++中的右值引用

    右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来. 从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题.从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷.从库设计者的角度讲,它给库设计者又带来了一把利器.从库使用者的角度讲,不动一兵一卒便可以获得"免费的"效率提升- 在标准C++语言中,临时量(术语为右值,因其出

  • C++左值与右值,右值引用,移动语义与完美转发详解

    目录 C++——左值与右值.右值引用.移动语义与完美转发 一.左值和右值的定义 二.如何判断一个表达式是左值还是右值(大多数场景) 三.C++右值引用 四.std::move()与移动语义 五. 完美转发 总结 C++——左值与右值.右值引用.移动语义与完美转发 在C++或者C语言中,一个表达式(可以是字面量.变量.对象.函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式. 一.左值和右值的定义 1.左值的英文为locator value,简写为lvalue,可意为存储在内存中.有明

  • 详解C++中的左值,纯右值和将亡值

    目录 引入 一.表达式 二.值类别 三.左值 四.纯右值 五.将亡值 六.注意 引入 C++中本身是存在左值,右值的概念,但是在C11中又出现了左值,纯右值,将亡值得概念:这里我们主要介绍这些值的概念. 一.表达式 定义:由运算符和运算对象构成的计算式(类似数学中的算术表达式) 每个 C++ 表达式(带有操作数的操作符.字面量.变量名等)可按照两种独立的特性加以辨别:**类型和值类别 **(value category).每个表达式都具有某种非引用类型,且每个表达式只属于三种基本值类别中的一种:

  • 浅析C++11中的右值引用、转移语义和完美转发

    1. 左值与右值: C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值. 可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值. 从本质上理解,创建和销毁由编译器幕后控制的,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象),例如: int& fo

随机推荐