斯科特迈尔斯关于Rvalueness

Kri*_*ato 8 c++ rvalue-reference move-semantics c++11

我观看了Scott Meyers关于Universal References的极其丰富的视频,其中我学到了大部分关于Rvalue引用,移动和转发的知识.有一次,他谈论的是左撇子而不是变量的类型,他说了一些"rvalueness与类型无关"的效果.

我知道你可以有这样的方法:

void func(MyType&& rRef)
{
    // Do domething with rRef...
}
Run Code Online (Sandbox Code Playgroud)

并且这rRef是一个左值,因为它可以被识别,它的地址可以被采取,等等,即使它的类型是MyType&&.

但rvalue不能是任何类型,可以吗?我的意思是,它只能是一个MyType&&,对吗?从这个意义上说,我认为类型并不完全独立于右值.也许我错过了什么.

更新:我的观点可以更加清晰.如果在func()我调用两个重载函数中的一个定义为

void gunc(MyType&& rRef)
{
   // ...
}

void gunc(MyType& lRef)
{
   // ...
}
Run Code Online (Sandbox Code Playgroud)

即通过调用gunc(std::move(rRef))gunc(rRef),似乎括号之间的结果表达式的类型不与rvalueness无关.

Joh*_*itb 4

表达式的类型没有任何引用的痕迹。因此,如果我们暂时假设引用可以具有引用类型,那么我们将得到以下结果

int a = 0;
int &ra = a;

int c = a + 42;
int d = ra + 42;
Run Code Online (Sandbox Code Playgroud)

在上面,表达式a 具有 type int,并且表达式ra 具有 type int&。我认为在几乎所有将表达式与类型相关的规范规则中,例如“表达式 E 必须是 X 类型”的规则,我们必须添加“...或对 X 类型的引用”(考虑一下强制转换运算符)。所以我有根据的猜测是,这会是一个太大的负担而没有用处。


C++有以下类型

  • 表达式的静态类型

只是称为“表达式的类型”(如果没有另外指定,则表示动态类型)。这是表达式的一个属性,它指定在编译时从表达式所引用的内容中抽象出来的表达式类型。例如,如果a引用int&orint变量,或者是文字0,则所有这些表达式都具有 type int

  • 左值表达式的动态类型

这是左值表达式引用的非基类对象所具有的类型。

ofstream fs("/log");
ostream &os = fs;
Run Code Online (Sandbox Code Playgroud)

其中,os静态类型 ostream动态类型 ofstream

  • 对象或引用的类型

这是对象或引用实际具有的类型。对象始终具有单一类型,并且其类型永远不会改变。但是什么对象存在于什么位置只有在运行时才知道,所以一般来说,“对象的类型”也是运行时的事情

ostream *os;
if(file)
  os = new ofstream("/log");
else
  os = new ostringstream;
Run Code Online (Sandbox Code Playgroud)

由 表示的对象类型*os(以及左值的动态类型*os)仅在运行时已知

int *p = new int[rand() % 5 + 1];
Run Code Online (Sandbox Code Playgroud)

这里,由operator new 创建的数组的类型也只有在运行时才知道,并且(谢天谢地)确实并且不能转义到静态C++ 类型系统。臭名昭著的别名规则(粗略地说,禁止从不兼容的左值读取对象)谈到了对象的“动态类型”,大概是因为它想强调运行时问题是令人感兴趣的。但严格来说,说对象的“动态类型”很奇怪,因为对象没有“静态类型”。

  • 变量或成员的声明类型

这是您在声明中给出的类型。相对于对象的类型或表达式的类型,有时可能会略有不同

struct A {
  A() { }
  int a;
};

const A *a = new const A;
volatile const A *va = a;
Run Code Online (Sandbox Code Playgroud)

此处,表达式a->a具有 type const int,但解析为的成员的声明类型a->a具有类型int(成员实体)。由 表示的对象的类型a->a具有 type const int,因为我们const A使用表达式创建了一个对象new,因此所有非静态数据成员都是隐const式子对象。在 中va->a,表达式具有类型volatile const int a,并且变量的声明类型仍然具有类型int,并且引用的对象的类型仍然具有类型const int


当您说“a 的类型”并将“a”声明为“int &a;”时 因此,你总是必须说出“a 类型”的含义。你指的是表情吗?那么“a”的类型为int。它甚至可能变得更糟糕。想象一下“int a[10];”。这里,表达式“a”具有类型int*,或者int[10]取决于当您询问“a 的类型”时,您是否认为表达式中发生了数组到指针的转换。如果您询问“a”引用的变量的类型,那么唯一的答案是intint[10]


那么右值可以是什么类型呢?右值是表达式。

int &&x = 0;
int y = std::move(x);
int z = x;
Run Code Online (Sandbox Code Playgroud)

在这里,我们有右值0std::move(x)。两个右值都有类型intx出现在初始化器中的表达式z是左值,即使它引用与右值引用的同一对象std::move(x)


关于分别使用右值或左值调用的重载函数的问题中的最后一点很有趣。仅仅因为右值引用被写为int &&并不意味着右值具有类型int。它们被称为右值引用,因为您可以使用右值初始化它们,并且该语言更喜欢初始化而不是使用右值初始化左值引用。


此外,查看名称形式的右值表达式可能很有用

enum A { X };
template<int Y> struct B { };
Run Code Online (Sandbox Code Playgroud)

如果您使用Xor Y,它们是右值。但我能想到的只有这些案例。