为什么这个函数需要static_cast<Object&&>?
Why is static_cast<Object&&> necessary in this function?
试图理解 std::move
,我发现 this answer 另一个问题。
说我有这个功能
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
我认为我理解的:
arg
是左值(值类别)。
arg
属于 类型 “对象的右值引用”。
static_cast
转换 类型 .
arg
和 return 类型都是 type “rvalue ref to Object”, static_cast
是不必要的。
但是,链接的答案是:
Now, you might wonder: do we even need the cast? The answer is: yes, we do. The reason is simple; named rvalue reference is treated as lvalue (and implicit conversion from lvalue to rvalue reference is forbidden by standard).
鉴于我上面所说的,我仍然不明白为什么 static_cast 是必要的。
the static_cast is unnecessary.
看似如此,却是必须的。您可以通过尝试在不进行强制转换的情况下编写此类函数来轻松找到答案,因为编译器应该告诉您该程序是 ill-formed。函数(模板)return 是一个右值引用。该右值引用不能绑定到左值。 id-expression arg
是左值(如您所述),因此 returned 右值引用不能绑定到它。
在 return 值上下文之外可能更容易理解。这里的规则是一样的:
T obj;
T&& rref0 = obj; // lvalue; not OK
T&& rref1 = static_cast<T&&>(obj); // xvalue; OK
T&& rref2 = rref1; // lvalue; not OK
T&& rref3 = static_cast<T&&>(rref1); // xvalue; OK
我有以下心智模型(让我们使用 int
而不是 Object
)。
有名字的物体是“坐在地上”。它们是左值;您不能将它们转换为右值引用。
int do_stuff(int x, int&& y) {...} // both x and y have a name
当你做计算时,你从地上捡起物体,在 mid-air 中做你的事情,然后把结果放回去。
x + y; // it's in mid-air
do_stuff(4, 5); // return value is in mid-air
这些临时结果可以转换为右值引用。但是一旦你“把它们放到地上”,它们就表现为左值。
int&& z = x + y; // on the ground
int&& z = do_stuff(6, 7); // on the ground
我确信它只在简单的情况下有用,但至少它给出了一些 real-world 类比 C++ 的工作方式。
您的第一个项目符号根本不正确,arg
不是左值,也不是右值。它既不是右值引用也不是左值引用,因为 std::move
是一个模板。在模板上下文中,如果 T
是 template-parameter,则类型 T&&
的函数参数是 转发引用 。转发引用变为适当的类型,具体取决于 T 是什么。
(and implicit conversion from lvalue to rvalue reference is forbidden by standard).
正因为如此,字面上需要演员表。以下代码不正确,因为您不能调用 foo(v)
,因为 v
是一个命名对象,它可以是一个左值:
void foo(int && a) { a = 5; }
int main()
{
int v;
foo(v);
std::cout << a << std::endl;
}
但如果 foo()
是一个模板,它可能会变成一个带有 int&
、const int&
和 int&&
个参数的函数。
template<class T>
void foo(T && a) { a = 5; }
您可以调用 foo(v+5)
,其中参数是一个临时参数,可以绑定到右值引用。 foo
将函数调用后停止存在的临时对象更改为存在。这正是移动构造函数通常必须执行的操作 - 在调用析构函数之前修改临时对象。
注意:右值参数将在使用后或函数调用结束时提前消失。
转发引用是一种特殊的引用语法,旨在保留函数参数的值类别。 IE。 non-template 函数
Object&& move(Object&& arg)
不等于 Object
的 std::move
,它声明了类似 (c++11):
template<class T>
std::remove_reference<T>::type&& move( T&& t );
在non-template函数中arg
是一个左值,在模板中它与用于初始化它的表达式具有相同的值类别。在模板中 std::remove_reference<T>::type
引用 T,因此 std::remove_reference<T>::type&&
是对 T 的真正右值引用 - 一种绕过 T&&
替代含义的方法。
类比上面的函数调用,如果隐式转换是可能的,那么就可以在复制构造函数合适但缺失的地方调用移动构造函数,即错误地调用。 return static_cast<Object&&>(arg);
导致初始化涉及根据 return
的定义调用 Object::Object(Object&&)
,return arg
将调用 Object::Object(const Object&)
.
模板 std::move
是 type-correct static_cast
的“包装器”,以促进“隐式”转换,通过从代码中删除具有显式类型的重复 static_cast
来简化代码.
试图理解 std::move
,我发现 this answer 另一个问题。
说我有这个功能
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
我认为我理解的:
arg
是左值(值类别)。arg
属于 类型 “对象的右值引用”。static_cast
转换 类型 .arg
和 return 类型都是 type “rvalue ref to Object”,static_cast
是不必要的。
但是,链接的答案是:
Now, you might wonder: do we even need the cast? The answer is: yes, we do. The reason is simple; named rvalue reference is treated as lvalue (and implicit conversion from lvalue to rvalue reference is forbidden by standard).
鉴于我上面所说的,我仍然不明白为什么 static_cast 是必要的。
the static_cast is unnecessary.
看似如此,却是必须的。您可以通过尝试在不进行强制转换的情况下编写此类函数来轻松找到答案,因为编译器应该告诉您该程序是 ill-formed。函数(模板)return 是一个右值引用。该右值引用不能绑定到左值。 id-expression arg
是左值(如您所述),因此 returned 右值引用不能绑定到它。
在 return 值上下文之外可能更容易理解。这里的规则是一样的:
T obj;
T&& rref0 = obj; // lvalue; not OK
T&& rref1 = static_cast<T&&>(obj); // xvalue; OK
T&& rref2 = rref1; // lvalue; not OK
T&& rref3 = static_cast<T&&>(rref1); // xvalue; OK
我有以下心智模型(让我们使用 int
而不是 Object
)。
有名字的物体是“坐在地上”。它们是左值;您不能将它们转换为右值引用。
int do_stuff(int x, int&& y) {...} // both x and y have a name
当你做计算时,你从地上捡起物体,在 mid-air 中做你的事情,然后把结果放回去。
x + y; // it's in mid-air
do_stuff(4, 5); // return value is in mid-air
这些临时结果可以转换为右值引用。但是一旦你“把它们放到地上”,它们就表现为左值。
int&& z = x + y; // on the ground
int&& z = do_stuff(6, 7); // on the ground
我确信它只在简单的情况下有用,但至少它给出了一些 real-world 类比 C++ 的工作方式。
您的第一个项目符号根本不正确,arg
不是左值,也不是右值。它既不是右值引用也不是左值引用,因为 std::move
是一个模板。在模板上下文中,如果 T
是 template-parameter,则类型 T&&
的函数参数是 转发引用 。转发引用变为适当的类型,具体取决于 T 是什么。
(and implicit conversion from lvalue to rvalue reference is forbidden by standard).
正因为如此,字面上需要演员表。以下代码不正确,因为您不能调用 foo(v)
,因为 v
是一个命名对象,它可以是一个左值:
void foo(int && a) { a = 5; }
int main()
{
int v;
foo(v);
std::cout << a << std::endl;
}
但如果 foo()
是一个模板,它可能会变成一个带有 int&
、const int&
和 int&&
个参数的函数。
template<class T>
void foo(T && a) { a = 5; }
您可以调用 foo(v+5)
,其中参数是一个临时参数,可以绑定到右值引用。 foo
将函数调用后停止存在的临时对象更改为存在。这正是移动构造函数通常必须执行的操作 - 在调用析构函数之前修改临时对象。
注意:右值参数将在使用后或函数调用结束时提前消失。
转发引用是一种特殊的引用语法,旨在保留函数参数的值类别。 IE。 non-template 函数
Object&& move(Object&& arg)
不等于 Object
的 std::move
,它声明了类似 (c++11):
template<class T>
std::remove_reference<T>::type&& move( T&& t );
在non-template函数中arg
是一个左值,在模板中它与用于初始化它的表达式具有相同的值类别。在模板中 std::remove_reference<T>::type
引用 T,因此 std::remove_reference<T>::type&&
是对 T 的真正右值引用 - 一种绕过 T&&
替代含义的方法。
类比上面的函数调用,如果隐式转换是可能的,那么就可以在复制构造函数合适但缺失的地方调用移动构造函数,即错误地调用。 return static_cast<Object&&>(arg);
导致初始化涉及根据 return
的定义调用 Object::Object(Object&&)
,return arg
将调用 Object::Object(const Object&)
.
模板 std::move
是 type-correct static_cast
的“包装器”,以促进“隐式”转换,通过从代码中删除具有显式类型的重复 static_cast
来简化代码.