我看到整个C++标准中许多地方使用的术语"左值到右值转换".据我所知,这种转换通常是隐含的.
标准中的一个意外(对我来说)特征是他们决定将左值作为转换处理.如果他们说glvalue总是可以接受而不是prvalue怎么办?这句话实际上会有不同的含义吗?例如,我们读到lvalues和xvalues是glvalues的例子.我们没有读到lvalues和xvalues可以转换为glvalues.意义上有区别吗?
在我第一次遇到这个术语之前,我曾经或多或少地对lvalues和rvalues进行了如下建模:"lvalues 总是能够充当rvalues,但另外还可以出现在左侧=和右侧&".
对我来说,这是一个直观的行为,如果我有一个变量名,那么我可以将该名称放在我想放置文字的地方.此模型似乎与标准中使用的左值到右值隐式转换术语一致,只要保证发生此隐式转换即可.
但是,因为他们使用这个术语,我开始想知道在某些情况下是否可能无法进行隐式左值到右值转换.也就是说,也许我的心理模型在这里是错误的.以下是该标准的相关部分:(感谢评论者).
每当glvalue出现在期望prvalue的上下文中时,glvalue就会转换为prvalue; 见4.1,4.2和4.3.[注意:尝试将rvalue引用绑定到左值不是这样的上下文; 见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 …Run Code Online (Sandbox Code Playgroud) 考虑一个 C++20 程序,其中函数中foo有一个结构化绑定auto [y]。函数返回y,它被转换为对象类型A。A可以从右值引用的常量引用构造。
#include <tuple>
#include <iostream>
struct A {
A(const int &) { std::cout << "A(const int &) "; }
A(int &&) { std::cout << "A(int &&) "; }
};
A foo() {
auto [y] = std::make_tuple(1);
return y;
}
int main() { foo(); }
Run Code Online (Sandbox Code Playgroud)
根据C++20语言标准应该选择哪一个构造函数?
Clang 选择A(const int &)和 GCC 选择A(int &&),演示:https : //gcc.godbolt.org/z/5q779vE6T
是否有一个编译器不支持这方面的标准?
TL; DR
给出以下代码:
int* ptr;
*ptr = 0;
Run Code Online (Sandbox Code Playgroud)
在应用间接之前,是否*ptr需要进行左值到右值的转换ptr?
该标准涵盖了许多地方的左值到左值的主题,但似乎没有指定足够的信息来确定*运算符是否需要这样的转换.
细节
在左值到右值的转换是覆盖在N3485的部分4.1 左值到右值转换段落1和说(重点矿山前进):
可以将非函数非数组类型T的glvalue(3.10)转换为prvalue.53如果T是不完整类型,则需要进行此转换的程序是错误的.如果glvalue引用的对象不是T类型的对象,并且不是从T派生的类型的对象,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为.[...]
那么*ptr = 0; 转换需要吗?
如果我们转到4第1节,它说:
[...] 如果需要,标准转换序列将应用于表达式 ,以将其转换为所需的目标类型.
那么什么时候需要呢?如果我们看一下5 表达式,第9段中提到了左值到右值的转换,它说:
每当glvalue表达式作为操作符的操作数出现时,该操作符需要该操作数的prvalue,左值到右值(4.1),数组到指针(4.2)或函数到指针(4.3)标准转换是用于将表达式转换为prvalue.[...]
和第11段说:
在某些情况下,表达式仅出现其副作用.这样的表达式称为丢弃值表达式.[...]当且仅当表达式是volatile限定类型的左值并且它是以下之一时,才应用左值到右值转换(4.1). ...]
两段似乎都不适用于此代码示例和5.3.1 一元运算符第1段它说:
一元*运算符执行间接:它所应用的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是指向表达式指向的对象或函数的左值.如果表达式的类型是"指向T的指针",则结果的类型为"T".[注意:通过指向不完整类型(cv void除外)的指向的间接有效.由此获得的左值可以以有限的方式使用(例如,初始化参考); 这个左值不能转换为prvalue,见4.1. - …
我试图std::swap从[C++ 11:utility.swap]中了解条件.模板定义为
template <typename T> void swap(T &, T &)
Run Code Online (Sandbox Code Playgroud)
(加上一些noexcept细节)并具有"交换存储在两个位置的值"的效果.
以下程序是否有明确定义?
#include <utility>
int main()
{
int m, n;
std::swap(m, n);
}
Run Code Online (Sandbox Code Playgroud)
如果我自己编写交换代码(即int tmp = m; m = n; n = tmp;),它将具有未定义的行为,因为它将尝试对未初始化的对象进行左值到右值的转换.但标准std::swap函数似乎没有任何强加于它的条件,也不能从规范中得出有任何左值到右值并因此有UB.
标准是否需要std::swap执行一些在未初始化对象上明确定义的魔法?
为了澄清这一点,请考虑函数void f(int & n) { n = 25; },该函数从未具有未定义的行为(因为它不读取n).
c++ undefined-behavior language-lawyer lvalue-to-rvalue c++11
在下面的代码中,我无法将临时对象作为参数传递给printAge函数:
struct Person {
int age;
Person(int _age): age(_age) {}
};
void printAge(Person &person) {
cout << "Age: " << person.age << endl;
}
int main () {
Person p(50);
printAge(Person(50)); // fails!
printAge(p);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我得到的错误是:
error: invalid initialization of non-const reference of type ‘Person&’ from an rvalue of type ‘Person’
Run Code Online (Sandbox Code Playgroud)
我意识到这与将lValue传递给期望rValue的函数有关...有没有办法通过使用std :: move或其他东西将我的lValue转换为rValue?我尝试了一个常量参数,但这似乎不起作用.
说我有以下功能
void doWork(Widget && param) // param is an LVALUE of RRef type
{
Widget store = std::move(param);
}
Run Code Online (Sandbox Code Playgroud)
为什么我需要param回到rvalue std::move()?是不是很明显,paramrvalue 的类型是因为它在函数签名中被声明为右值引用?不应该仅仅根据这个原则自动调用移动构造函数吗?
为什么不默认发生这种情况?
c++ rvalue-reference move-constructor lvalue-to-rvalue stdmove
C++03§4.2N°1:
类型为"NT的数组"或"T的未知边界数组"的左值或右值可以转换为"指向T的指针"的右值.结果是指向数组的第一个元素.
很长一段时间以来我在这个陈述中令人困惑的是,我不太明白阵列类型的右值是什么意思.也就是说,我无法想出一个表达式,其类型是一个数组,结果是一个右值.我读了这个帖子,基本上问了同样的问题,接受的答案是"不,没有数组类型的右值".我想我可能与此矛盾.
C++03§5.2.5N°4 :(关于表达式E1.E2)
如果E2是非静态数据成员,E1的类型是"cq1 vq1 X",E2的类型是"cq2" vq2 T",表达式指定由第一个表达式指定的对象的命名成员.如果E1是左值,则E1.E2是左值.
我认为否则它是一个右值(假设E2不是参考,那个案例被覆盖§5.2.5 N°3)因此......
struct A
{
int a[4];
};
A f()
{
A a;
return a;
}
int main()
{
f().a; //I think this is an rvalue of array type...
}
Run Code Online (Sandbox Code Playgroud)
我在这里看到两个选项:
选项1:我是正确的,欢呼,是的,很酷.在这种情况下,问题是:还有其他例子吗?
选项2:我不对,在这种情况下问题是:这是标准的缺陷吗?
我不知道1,但我真的怀疑2,因为当他们谈论函数到指针的转换时,他们只提到了函数类型的左值(很明显没有这样的rvalues).所以他们很可能已经考虑了数组类型的rvalues.
所以,基本上我的问题是我是否提出了数组类型右值的例子,如果没有,请提供一个有效的,我坚信它存在.
今天我大致遇到了以下代码:
\n#include <iostream>\n\nvoid f(float&& f) { std::cout << f << "f "; }\nvoid f(int&& i) { std::cout << i << "i "; }\n\nint main()\n{\n int iv = 2; float fv = 1.0f;\n f(2); f(1.0f);\n f(iv); f(fv);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n正如预期的那样,前两个 f 调用 print 2i 1f。
现在对于第二行,我预计它要么根本不编译,因为 iv 和 fv 不是临时变量(因此不能绑定到 r 值引用),要么它创建要传递给函数的变量的副本,从而打印2i 1f再次打印。
然而,不知何故它打印2f 1i,这几乎是我最没想到的事情。
如果将代码复制到 cppinsights 中,它会将调用转换为
\nf(static_cast<float>(iv));\nf(static_cast<int>(fv));\nRun Code Online (Sandbox Code Playgroud)\n所以它看起来非常有意地将整数转换为浮点数,然后将浮点数转换为整数,但我不知道为什么这样做,也不知道如何谷歌。为什么会出现这种情况?导致这个结果的规则是什么?
\n$ 4.2/1 - "类型的左值或右值"数组"N T"或"未知T的数组"可以转换为"指向T的指针"的右值.结果是一个指向第一个元素的指针阵".
我不知道如何在初始化/声明期间获得数组类型的rvalue?
在C++ 14标准(n3797)中,左值转换的左值部分如下所示(强调我的):
4.1左值 - 右值 - 转换[conv.lval]
T可以将非函数非数组类型的glvalue(3.10)转换为prvalue.如果T是不完整类型,则需要进行此转换的程序格式不正确.如果T是非类类型,则prvalue的类型是cv-nonqualified versionT.否则prvalue的类型是T.当在未评估的操作数或其子表达式中发生左值到右值转换时(第5条),不访问引用对象中包含的值.在所有其他情况下,转换结果根据以下规则确定:
- 如果
T是(可能是cv限定的)std::nullptr_t则结果是空指针常量.- 否则,如果
T有类类型,则转换复制 -T从glvalue 初始化一个临时类型,转换的结果是临时的prvalue.- 否则,如果glvalue引用的对象包含无效指针值,则行为是实现定义的.
- 否则,如果
T是(可能是cv限定的)无符号字符类型,并且glvalue引用的对象包含不确定的值,并且该对象没有自动存储持续时间,或者glvalue是一元运算&符的操作数,或者它是绑定到引用,结果是一个未指定的值.- 否则,如果glvalue引用的对象具有不确定的值,则行为未定义.
- 否则,glvalue指示的对象是prvalue结果.
- [ 注:另见3.10]
这一段有什么意义(粗体)?
如果此段落不在此处,那么它适用的情况将导致未定义的行为.通常,我希望unsigned char在具有不确定值时访问值会导致未定义的行为.但是,这一段意味着
&或将其绑定到引用,或者unsigned char没有自动存储时间,然后转换产生一个未指定的值,而不是未定义的行为.
我是否正确地得出这个程序的结论:
#include <new>
#include <iostream>
// using T = int;
using T = unsigned char;
int main() {
T * array = new T[500];
for …Run Code Online (Sandbox Code Playgroud) c++ undefined-behavior language-lawyer lvalue-to-rvalue c++14
c++ ×10
lvalue-to-rvalue ×10
arrays ×2
c++11 ×2
rvalue ×2
c++14 ×1
c++20 ×1
indirection ×1
lvalue ×1
overloading ×1
pointers ×1
reference ×1
stdmove ×1