#include <iostream>
int main() {
bool b = true;
std::cout << std::is_same<decltype(!(!b)), bool>::value << "\n";
auto bb = (!(!b));
std::cout << std::is_same<decltype(bb), bool>::value << "\n";
}
Run Code Online (Sandbox Code Playgroud)
上面的代码使用不同的编译器有不同的结果。这是编译器错误还是我遗漏了什么?
我不能这样做:
int &&q = 7;
int &&r = q;
//Error Message:
//cannot convert from 'int' to 'int &&'
//You cannot bind an lvalue to an rvalue reference
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,在初始化右值引用时,也会初始化一个临时变量.所以int &&q = 7;可以认为是:
int temp = 7;
int &&q = temp;
Run Code Online (Sandbox Code Playgroud)
当在右侧使用参考时,我实际上是在使用裁判.所以int &&r = q;可以认为是:
int &&r = temp; //bind an lvalue to an rvalue reference, cause error, understandable
Run Code Online (Sandbox Code Playgroud)
所以上面是我理解编译器错误发生的方式.
为什么添加std::forward可以解决?
int &&q = 7;
int &&r = std::forward<int>(q);
Run Code Online (Sandbox Code Playgroud)
我知道std::forward总是返回一个右值引用,引用的引用是如何std::forward不同的int&&q?
test对于左值空字符串,左值非空字符串和右值字符串,下面的函数已重载。我尝试使用Clang和GCC进行编译,但在两种情况下都没有达到我期望的结果。
#include <iostream>
void test(const char (&)[1]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <unsigned long int N>
void test(const char (&)[N]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
void test(char*&&){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
int main(){
char str1[] = "";
char str2[] = "test";
test("");
test("test");
test(str1);
test(str2);
}
Run Code Online (Sandbox Code Playgroud)
使用clang 版本6.0.0-1ubuntu2的输出:
#include <iostream>
void test(const char (&)[1]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <unsigned long int N>
void test(const char (&)[N]){ std::cout << __PRETTY_FUNCTION__ << …Run Code Online (Sandbox Code Playgroud) 人们听到他们的声音感到困惑
int&& x
Run Code Online (Sandbox Code Playgroud)
x具有右值引用类型,但x为左值。误解源于标识符和表达式是不同的事物,类型和值类别也是如此。此外,表达式的类型“在进行任何进一步分析之前已调整”,并且单词“ rvalue”和“ lvalue”可以同时出现在类型名称和值类别名称中。
我想澄清正式定义。假设我们有一个函数:
1 | void f(int&& x) {
2 | ... = x;
3 | ... = std::move(x);
4 | }
Run Code Online (Sandbox Code Playgroud)
以下陈述正确吗?
x是一个标识符(id表达式),它为函数参数命名。其类型为int&&,这是decltype(x)返回的类型。x不是表达式,没有值类别。x是一个表达式。在类型调整之前,其类型为int&&,而在类型变为之后int。值类别为左值。std::move(x)是一个表达式。调整前的类型是int&&-之后int。值类别为xvalue。x具有右值引用类型时,在类型调整之前,我们将其x称为标识符,或者将其x称为表达式。T&或const T&,等等),则该表达式是左值。” 他是指调整前的类型,第二个单词“左值”是指值类别。你好 stackoverflow 社区,
我已经学习 C++ 几个月了,最近我一直在尝试掌握围绕“新”值类别、移动语义,尤其是临时物化的概念。
首先,如何解释“临时物化转换”这个词对我来说并不简单。转换部分对我来说很清楚(prvalue -> xvalue)。但在这种情况下,“临时”究竟是如何定义的呢?我曾经认为临时对象是仅存在的未命名对象 - 从语言的角度来看 - 直到评估它们所创建的表达式的最后一步。但这种概念似乎与临时对象的实际情况不符在临时物化、新价值类别等更广泛的背景下。
由于对“临时”一词缺乏明确性,导致我无法判断“临时物化”是临时物化还是临时物化。我认为是前者,但我不确定。另外:术语“临时”仅用于类类型吗?
这直接给我带来了下一个困惑:prvalues 和 xvalues 对于临时变量起什么作用?假设我有一个右值表达式,需要以必须将其转换为 xvalue 的方式进行计算,例如通过执行成员访问。究竟会发生什么?纯右值是否实际存在(在内存中或其他地方)并且纯右值是否已经是临时的?现在,“临时物化转换”被描述为“任何完整类型 T 的纯右值都可以转换为相同类型 T 的 xvalue。此转换通过使用临时对象评估纯右值,从纯右值初始化类型 T 的临时对象作为其结果对象,并生成一个表示临时对象的 xvalue”(https://en.cppreference.com/w/cpp/language/implicit_conversion)将纯右值转换为 xvalue。这段摘录让我认为纯右值是在内存或寄存器中任何地方都不存在的东西,直到它通过这种转换“具体化”为止。(另外,我不确定临时对象是否与临时对象相同。)因此,据我了解,此转换是通过对纯右值表达式进行求值来完成的,该表达式的结果是“真实”对象。然后,该对象由 xvalue 表达式表示(= 表示?)。记忆中发生了什么?右值在哪里,x值现在在哪里?
我的下一个问题是关于临时物化的某个部分的更具体的问题。在 Kris van Rens 在 YouTube ( https://www.youtube.com/watch?v=liAnuOfc66o&t=3576s ) 上大约 56:30 的演讲“Understanding valuecategories in C++”中,他展示了这张幻灯片:
根据 cppreference.com 关于临时物化的说法,数字 1 和 2 是明确的情况(1:类 pravlue 上的成员访问,2:将引用绑定到纯右值(如 std::string + 运算符中)。
不过,我对第三个不太确定。Cppreference 说:“请注意,当从相同类型的纯右值(通过直接初始化或复制初始化)初始化对象时,不会发生临时物化:此类对象是直接从初始化器初始化的。这确保了“保证复制省略”。 ” + 运算符返回纯右值。现在,这个 std::string 类型的纯右值用于初始化一个 auto(也应该解析为 std::string)变量。这听起来像是前面的 cppreference 摘录中讨论的情况。那么这里真的会出现临时实体化吗?由中间的 xvalue 表达式“表示”的对象(1 和 2)会发生什么情况?他们什么时候被摧毁?如果 + 运算符返回纯右值,它是否“存在”在某个地方?如果纯右值甚至不是真实的(物化?)对象,那么对象 auto …
我一直在试图理解何时何时不具有捕获默认odr的lambda-使用在其周围范围内定义的自动存储持续时间的变量(由此答案提示).在探索周围时,我偶然发现了一点点好奇心.GCC和Clang似乎不同意n以下代码中id-expression的值类别:
template <typename T> void assert_is_lvalue(const T&) {}
template <typename T> void assert_is_lvalue(const T&&) = delete;
int main() {
const int n = 0;
[=] { assert_is_lvalue(n); };
}
Run Code Online (Sandbox Code Playgroud)
Clang成功编译代码,而GCC不编译(error: use of deleted function).哪一个是正确的?或者这是未指定或实现定义的东西?
绑定对象的引用应该使用它,并且通过删除lambda的capture-default并观察两个编译器然后抱怨在n没有捕获默认值的情况下不能隐式捕获来确认.
将lambda标记为mutable编译器的输出没有明显的差别.
我对这个问题的广泛性感到抱歉,只是所有这些细节都是紧密相连的。
我一直在尝试理解两个特定值类别 - xvalues 和 prvalues 之间的区别,但我仍然感到困惑。
不管怎样,我试图为自己开发的“同一性”概念的心智模型是,应该保证具有同一性的表达式驻留在实际程序的数据存储器中。
由于这个原因,字符串文字是左值,它们保证在整个程序运行期间驻留在内存中,而数字文字是纯右值,并且可以假设存储在直接汇编中。
这似乎同样适用于std::move纯纯右值文字,即,在调用时,fun(1)我们只会在被调用者框架中获得参数左值,但在调用时,fun(std::move(1))左值的 xvalue“种类”必须保留在调用者框架中。
然而,这种心理模型至少不适用于临时对象,据我所知,临时对象应该始终在实际内存中创建(例如,如果像使用纯右值参数那样调用fun(MyClass())右值引用函数)。所以我认为这种思维模式是错误的。
那么,思考 xvalue 的“同一性”属性的正确方法是什么?我已经读过,通过身份我可以比较地址,但是如果我可以比较 2 的地址MyClass().member(根据 cppreference 的 xvalue),比方说通过右值引用将它们传递到某个比较函数中,那么我不明白为什么我可以不对 2 MyClass()s(纯右值)做同样的事情吗?
与此相关的另一个来源是此处的答案: 什么是移动语义?
请注意,尽管 std::move(a) 是右值,但其求值不会创建临时对象。这个难题迫使委员会引入第三个价值类别。可以绑定到右值引用的东西,即使它不是传统意义上的右值,也称为xvalue(到期值)。
但这似乎与“可以比较地址”无关,并且a)我不明白这与右值的“传统意义上”有什么不同;b) 我不明白为什么这样的原因需要语言中的新值类别(好吧,这允许为 OO 意义上的对象提供动态类型,但 xvalue 不仅仅指对象)。
假设我们有一个带有 2 个 xvalue 操作数的三元运算符。
struct A {
A() { std::cout<<"A ctor"<<std::endl; }
~A() { std::cout<<"A dtor"<<std::endl; }
A(A const&) { std::cout<<"A copy ctor"<<std::endl; }
A(A&&) { std::cout<<"A move ctor"<<std::endl; }
void foo() & { std::cout<<"A&.foo()"<<std::endl; }
void foo() const& { std::cout<<"A const&.foo()"<<std::endl; }
void foo() && { std::cout<<"A&&.foo()"<<std::endl; }
void foo() const&& { std::cout<<"A const&&.foo()"<<std::endl; }
};
int main()
{
A a;
A a2;
(true? static_cast<A&&>(a) : static_cast<A&&>(a2)).foo();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
根据cppreference条件运算符
4) 如果 E2 和 E3 是相同类型和相同值类别的左值,则结果具有相同类型和值类别,并且如果 E2 …
我知道命名引用是左值:
int x = 1;
int& ref1 = x;
int&& ref2 = std::move(x);
Run Code Online (Sandbox Code Playgroud)
我读过解释——那是因为我们可以获取那些ref1和的地址ref2。
但是当我们获取引用的地址时,我们实际上获取了被引用对象的地址,不是吗?所以这个解释似乎不太正确。
那么为什么命名引用是左值呢?
考虑以下代码:
struct S
{
S& operator=(const S&) = default;
};
int main()
{
&(S() = S());
}
Run Code Online (Sandbox Code Playgroud)
GCC(主干)抱怨:
Run Code Online (Sandbox Code Playgroud)error: taking address of rvalue [-fpermissive] 8 | &(S() = S()); | ~~~~~^~~~~~
我发现上述错误令人惊讶,因为我希望S&返回类型可以生成左值表达式。另一方面,Clang(主干)在没有任何错误或警告的情况下接受代码。
什么编译器在这里是正确的?
是S() = S()右值还是左值?