c++ 左值到右值隐式转换

wi3ka0sx  于 2023-06-07  发布在  其他
关注(0)|答案(2)|浏览(149)

我看到术语“左值到右值转换”在整个C++标准的许多地方使用。据我所知,这种转换通常是隐式完成的。
标准中的一个意想不到的(对我来说)特征是,他们决定将左值到右值视为转换。如果他们说glvalue总是可以接受的,而不是prvalue。这句话实际上有不同的含义吗?例如,我们读到左值和x值是左值的例子。我们没有读到左值和x值可以转换为左值。意思有区别吗?
在我第一次遇到这个术语之前,我曾经在脑海中或多或少地对左值和右值进行建模,如下所示:“左值 * 总是 * 能够充当右值,但另外可以出现在=的左侧和&的右侧”。
对我来说,这是一个直观的行为,如果我有一个变量名,那么我可以把这个名字放在任何地方,我会把一个字面量。这个模型似乎与标准中使用的左值到右值隐式转换术语一致,只要这种隐式转换能够保证发生。
但是,因为他们使用这个术语,我开始怀疑隐式的左值到右值转换是否在某些情况下可能无法发生。也就是说,也许我的思维模式是错误的。以下是标准的相关部分:(感谢评论者)。
每当左值出现在需要纯右值的上下文中时,左值被转换为纯右值;见4.1、4.2和4.3。[注意:试图将右值引用绑定到左值不是这样的上下文;见8.5.3 .-尾注]
我理解他们在笔记中描述的内容如下:

int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit 
// lvalue to rvalue conversion did not happen.  
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good).

我的问题是():
1)有人能澄清一下这种转换可能隐含发生的背景吗?具体地说,除了绑定到右值引用的上下文之外,是否还有其他的左值到右值的转换不能隐式地发生?
2)此外,从句中的括号[Note:...]使我们似乎可以从前面的句子中找到它。这是标准的哪一部分?
3)这是否意味着右值引用绑定不是我们期望的纯右值表达式(在右边)的上下文?
4)像其他转换一样,glvalue-to-prvalue转换是否涉及允许我观察它的运行时工作?
我在此的目的,并不是要问容许这种转变是否可取。我试着用标准作为出发点来向自己解释这段代码的行为。
一个好的答案应该是通过我上面的引用,并解释(基于对文本的解析)其中的注解是否也隐含在文本中。然后,它可能会添加任何其他引号,让我知道这种转换可能无法隐式发生的其他上下文,或者解释没有更多这样的上下文。也许是关于为什么glvalue到prvalue被认为是一种转换的一般性讨论。

rdlzhqv9

rdlzhqv91#

我认为左值到右值的转换不仅仅是 * 在需要右值的地方使用左值 *。它可以创建一个类的副本,并且总是产生一个 * 值 *,而不是一个对象。
我用n3485表示“C++11”,用n1256表示“C99”。

对象和值

最简洁的描述见C99/3.14:

    • object**

执行环境中的数据存储区域,其内容可以表示值
在C ++11/[intro.object]/1中也有一点
有些对象是多态的;该实现生成与每个这样的对象相关联的信息,该信息使得有可能在程序执行期间确定该对象的类型。对于其他对象,其中找到的值的解释由用于访问它们的表达式的类型确定。
所以一个对象包含一个值(可以包含)。

值类别

尽管它的名字,* 值类别 * 分类表达式,而不是值。lvalue-expressions even * cannot * be considered values.
完整的分类/归类可以在[basic.lval]中找到; here's a StackOverflow discussion
以下是关于对象的部分:

  • 一个 * lvalue *([...])表示一个函数或一个对象。[...]
    • xvalue *(“eXpiring”值)也指对象[...]
    • glvalue *(“广义”左值)是左值或x值。
    • rvalue *([...])是一个xvalue,一个临时对象或其子对象,或者一个与对象无关的值。
  • 纯右值("pure" rvalue)是一个不是xvalue的右值。[...]

请注意短语“与对象不关联的值”。还要注意,由于xvalue-expressions引用对象,因此true * values * 必须始终作为prvalue-expressions出现。

左值转右值

如脚注53所示,现在应称为"glvalue-to-prvalue转换"。首先,这是一句话:
1 非函数、非数组类型T的glvalue可以转换为纯右值。如果T是一个不完整的类型,则需要这种转换的程序是病态的。如果glvalue引用的对象不是T类型的对象,也不是从T派生的类型的对象,或者如果对象未初始化,则需要这种转换的程序具有未定义的行为。如果T是非类类型,则纯右值的类型是T的cv非限定版本。否则,纯右值的类型为T
第一段规定了转换的要求和结果类型。它还不关心转换的 * 效果 *(除了未定义的行为)。
2 当左值到右值的转换发生在未求值的操作数或其子表达式中时,被引用对象中包含的值不会被访问。否则,如果glvalue具有类类型,则转换从glvalue复制初始化T类型的临时值,并且转换的结果是临时值的纯右值。否则,如果glvalue具有(可能是cv限定的)类型std::nullptr_t,则纯右值结果是空指针常量。否则,由glvalue指示的对象中包含的值是纯右值结果。
我认为你会看到左值到右值的转换最常应用于非类类型。比如说

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

表达式x = y * 不 * 将左值到右值的转换应用于y(顺便说一下,这将创建一个临时的my_class)。原因是x = y被解释为x.operator=(y),它默认 * 通过引用 * 而不是 * 通过值 * 获取y(关于引用绑定,请参见下文;它不能绑定右值,因为这将是一个不同于y的临时对象)。但是,my_class::operator=的默认定义确实将左值到右值的转换应用于x.m
因此,对我来说最重要的部分似乎是
否则,由glvalue指示的对象中包含的值是纯右值结果。
因此,通常情况下,左值到右值的转换只是 * 从对象中读取值 *。它不仅仅是值(表达式)类别之间的无操作转换;它甚至可以通过调用复制构造函数来创建临时。左值到右值的转换总是返回一个纯右值 * 值 ,而不是一个(临时) 对象 *。
请注意,左值到右值的转换并不是将左值转换为纯右值的唯一转换:还有数组到指针的转换和函数到指针的转换。

值和表达式

大多数表达式不产生对象。然而,* id-表达式 * 可以是 * 标识符 *,其表示 * 实体 *。一个对象是一个实体,所以有一些表达式可以产生对象:

int x;
x = 5;
  • assignment-expression * x = 5的左边也需要是一个表达式。这里的x是一个 * id-expression *,因为x是一个标识符。这个 * id-expression * 的结果是 * 由x * 表示的对象。

表达式应用隐式转换:[expr]/9

每当一个左值表达式作为一个操作数出现,而该操作数需要一个纯右值时,左值到右值、数组到指针或函数到指针的标准转换都被应用于将表达式转换为纯右值。
和/10关于 * 通常的算术转换 * 以及/3关于用户定义的转换。
我现在想引用一个操作符,它“期望该操作数有一个纯右值”,但除了转换之外找不到任何纯右值。例如,[expr.dynamic.cast]/2“如果T是指针类型,则v [操作数]应是指向完整类类型的指针的纯右值”。
许多算术运算符所需的 * 通常算术转换 * 确实通过所使用的标准转换间接调用左值到右值转换。除了从左值转换为右值的三种转换之外,所有标准转换都需要纯右值。
然而,简单赋值并不调用通常的算术转换。它在[expr.ass]/2中定义为:
在简单赋值(=)中,表达式的值替换左操作数引用的对象的值。
因此,尽管它没有显式地要求右侧的纯右值表达式,但它确实需要一个 value。我不清楚这是否 * 严格 * 需要左值到右值的转换。有一个论点认为,访问未初始化变量的值应该总是调用未定义的行为(也请参见CWG 616),无论是通过将其值分配给对象还是将其值添加到另一个值。但是这种未定义的行为只在左值到右值转换(AFAIK)时需要,这应该是访问存储在对象中的值的唯一方法。
如果这个概念性的观点是正确的,我们需要左值到右值的转换来访问对象内部的值,那么理解它在哪里(以及需要在哪里)应用就容易得多了。

初始化

与简单赋值一样,有一个discussion是否需要左值到右值的转换来初始化另一个对象:

int x = 42; // initializer is a non-string literal -> prvalue
int y = x;  // initializer is an object / lvalue

对于基本类型,[dcl.init]/17最后一个要点是:
否则,被初始化的对象的初始值就是初始化器表达式的值(可能被转换)。如有必要,将使用标准转换将初始值设定项表达式转换为目标类型的cv无限定版本;不考虑用户定义的转换。如果无法完成转换,则初始化是格式错误的。
但是,它也提到了初始化表达式 * 的 * 值。与simple-assignment-expression类似,我们可以将其视为左值到右值转换的间接调用。

引用绑定

如果我们把左值到右值的转换看作是访问对象值的一种方式(加上为类类型操作数创建临时值),我们就明白它通常不适用于绑定到引用:引用是一个左值,它总是指向一个对象。因此,如果我们将值绑定到引用,我们需要创建临时对象来保存这些值。如果引用的初始化器表达式是纯右值(这是一个值或临时对象),情况确实如此:

int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42;      // same

禁止将纯右值绑定到左值引用,但是具有产生左值引用的转换函数的类类型的纯右值可以绑定到转换类型的左值引用。
在[dcl.init.ref]中对引用绑定的完整描述相当长,而且离题了。我认为它与这个问题有关的本质是引用引用对象,因此没有glvalue-to-prvalue(对象到值)转换。

2admgd59

2admgd592#

在glvalues上:glvalue(“generalized”lvalue)是一个表达式,它可以是一个左值,也可以是一个x值。一个glvalue可以通过左值到右值、数组到指针或函数到指针的隐式转换隐式转换为纯右值。
当左值参数(例如对对象的引用)在右值(例如,一个数字)。

左值转右值

任何非函数、非数组类型T的glvalue都可以隐式转换为相同类型的纯右值**。如果T是非类类型,则此转换还将删除cv限定符。除非在未求值的上下文中遇到(在sizeof、typeid、noexcept或decltype的操作数中),否则该转换使用原始glvalue作为构造函数参数有效地复制构造了T类型的临时对象,并且该临时对象作为纯右值返回。如果glvalue的类型为std::nullptr_t,则结果纯右值为空指针常量nullptr

相关问题