假设我有一个函数f(T&&)
的两个重载。 f(T&)
和g
。
然后在g(T&& t) { f(t);}
的正文中:
f(T&)
将调用重载f(T&&)
,因为t被认为是左值。
这对我来说非常令人惊讶。签名T&&
的功能如何与类型为f(static_cast<T&&>(t))
的呼叫不匹配?
令我更加惊讶的是,呼叫f(T&&)
实际上会调用右值超载T&&
。
使这成为可能的C ++规则是什么? {{1}}不仅仅是一种类型吗?
答案 0 :(得分:12)
自动视为rvalues的东西是没有名字的东西,以及(很快)没有名字的东西(在返回值的情况下)。
T&& t
有一个名称,它是t
。
rvalues就是那些东西的原因是在之后引用它们这个使用点几乎是不可能的。
T&&
是rvalue类型引用。右值引用只能绑定到右值(不涉及static_cast
),但它是rvalue引用类型的左值。
rvalue引用类型的事实仅在构造期间很重要,如果你做decltype(variable_name)
。否则它只是引用类型的另一个左值。
std::move(t)
执行return static_cast<T&&>(t);
并返回右值引用。
管理这一规则的规则是在C ++标准中标准化的。复制/粘贴它们并不是很有用,因为它们不容易理解。
第一个一般规则是,当您从函数返回命名值时,或者当某个值没有名称时,或者函数显式返回时,您将获得一个隐式移动(也就是绑定到右值引用参数的参数)右值参考。
其次,只有右值引用和const&
才能绑定到右值。
第三,当直接绑定到构造函数外部的引用时,会发生临时值的引用生存期扩展。 (因为只有右值引用和const&
可以直接绑定到临时值,这只适用于它们)
Forth,T&&
并不总是左值参考。如果T
的类型为X&
或X const&
,则引用折叠会将T&&
变为X&
或X const&
。
最后,类型扣除上下文中的T&&
会将T
推断为X
,X&
,X const&
或X const&&
,具体取决于类型参数,因此可以作为转发参考&#34;。
答案 1 :(得分:6)
当您按名称引用变量时,您总是得到一个左值。此规则没有例外,但请注意它不适用于预处理器宏,枚举器或非类型模板参数,这些参数都不是通常意义上的变量。
我认为虽然这种行为起初似乎没有意义,但当你仔细考虑它时,它确实有意义并且是正确的行为。首先,我们应该观察到值类别显然是表达式的属性,而不是对象本身的属性。这是显而易见的,因为std::move
永远不会创建新对象,而只是创建一个引用给定对象的右值表达式。然后我们应该明白:
T{}
与后面表达式中的T{}
不同;两者都创建匿名对象,但两者都是不同的,因此后者不会访问与前者相同的对象。 )引用对象的表达式的值类别因此是 relative; 它取决于特定的表达式和范围。 std::move
表示您不打算在同一范围内再次访问对象的值,允许被调用函数将其值移出该对象。但是,当被调用函数访问右值引用的名称时,该值在函数调用期间是永久; 该函数可以在任何时候将值移出该对象,或者根本不移动,但是无论如何,它可能将在体内访问它,在参数初始化之后。
在这个例子中:
void f(Foo&& foo) { /* use foo */ }
void g() {
Foo foo;
f(std::move(foo));
}
虽然std::move(foo)
中的g
和被调用者中的参数foo
引用同一个对象,但该对象的值即将消失 std::move
中的g
,而f
中的foo
,该对象的值预计会通过f
访问,可能会在struct Foo {
void f() &;
void f() &&;
void g() && {
f(); // calls lvalue-qualified f
}
};
void h() {
Foo().g();
}
结束前多次访问。
调用ref-qualified成员函数时存在类似的情况。
Foo()
此处,h()
的值即将从Foo::g()
消失;它将在完整表达式之后无法使用。但是,在g()
的正文中,它在*this
结束之前是永久性的; g()
可靠地访问同一对象的值。因此,当f()
调用f()
时,它应该调用期望左值的重载,*this*
不应该从g()
窃取g()
的值,这是很自然的。因为{{1}}可能仍想访问它。
答案 2 :(得分:4)
在g()
t
中是一个命名变量。所有命名变量都是左值。如果T
是模板类型,则可以使用std::forward
将变量转发至f()
。这会将f()
调用与传递给g()
template<typename T>
g(T&& t) { f(std::forward<T>(t));}
如果T
不是模板类型而只是一种类型,那么您可以使用std::move
g(T&& t) { f(std:move(t)); }