std::move 如何将表达式转换为右值?

发布于 2024-12-05 14:56:18 字数 1163 浏览 1 评论 0原文

我不完全理解 std::move() 的实现。 也就是说,我对 MSVC 标准库中的这种实现感到困惑:

模板排队
类型名 tr1::_Remove_reference<_Ty>::_Type&&
移动(_Ty&&_Arg)
{ // 将 _Arg 转发为可移动
    return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}

当我像这样调用 std::move 时:

Object obj1;
Object obj2 = std::move(obj1); // _Ty&& _Arg binds to obj1

... _Arg 引用参数绑定到左值 obj1。 您不能直接将右值引用绑定到左值,这让我认为需要强制转换为像 (Object&&) 这样的右值引用。 然而,这是荒谬的,因为 std::move() 必须适用于所有值。

为了完全理解它是如何工作的,我也研究了 std::remove_reference 的实现:

模板
结构体_Remove_reference
{ // 删除引用
    typedef _Ty _Type;
};

模板<类_Ty>
结构_Remove_reference<_Ty>
{ // 删除引用
    typedef _Ty _Type;
};

模板<类_Ty>
结构体_Remove_reference<_Ty&>
{ // 删除右值引用
    typedef _Ty _Type;
};

不幸的是,它仍然令人困惑,我不明白。 请帮助我理解 std::move 的实现。

I don't fully understand the implementation of std::move().
Namely, I am confused by this implementation in the MSVC standard library:

template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
{ // forward _Arg as movable
    return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}

When I call std::move like this:

Object obj1;
Object obj2 = std::move(obj1); // _Ty&& _Arg binds to obj1

... the _Arg reference parameter binds to the lvalue obj1.
You cannot directly bind an rvalue reference to an lvalue, which makes me think that a cast to an rvalue reference like (Object&&) is required.
However, this is absurd because std::move() must work for all the values.

To fully understand how this works, I've looked at the implementation of std::remove_reference too:

template<class _Ty>
struct _Remove_reference
{ // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&>
{ // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&&>
{ // remove rvalue reference
    typedef _Ty _Type;
};

Unfortunately it's still as confusing and I don't get it.
Please help me understand the implementation of std::move.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一影成城 2024-12-12 14:56:18

我们从 move 函数开始(我对其进行了一些清理):

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

让我们从更简单的部分开始 - 也就是说,当使用 rvalue: 调用该函数时

Object a = std::move(Object());
// Object() is temporary, which is prvalue

,我们的 move 模板将被实例化,如下所示:

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

由于 remove_referenceT& 转换为 TT&& 转换为 T,而 Object 不是参考,我们的最终函数是:

Object&& move(Object&& arg)
{
  return static_cast<Object&&>(arg);
}

现在,您可能想知道:我们还需要演员阵容吗?答案是:是的,我们愿意。原因很简单;命名右值引用被视为左值(标准禁止从左值到右值引用的隐式转换)。


时发生的情况

Object a; // a is lvalue
Object b = std::move(a);

以下是当我们使用左值调用 move和相应的 move 实例化

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

:同样,remove_referenceObject& 转换为Object 我们得到:

Object&& move(Object& && arg)
{
  return static_cast<Object&&>(arg);
}

现在我们到了棘手的部分:Object& 是什么? && 甚至意味着它如何绑定到左值?

为了实现完美的转发,C++11标准为引用折叠提供了特殊的规则,如下所示:

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

如您所见,在这些规则下 Object& && 实际上意味着 Object&,它是允许绑定左值的普通左值引用。

最终函数是这样的:

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

这与之前使用右值的实例化没有什么不同 - 它们都将其参数转换为右值引用,然后返回它。不同之处在于,第一个实例化只能与右值一起使用,而第二个实例化则与左值一起使用。


为了解释为什么我们还需要 remove_reference ,让我们尝试一下这个函数

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

并用左值实例化它。

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

应用上面提到的引用折叠规则,你可以看到我们得到了无法使用 move 的函数(简单来说,你用左值调用它,你会得到左值)。如果有的话,这个函数就是恒等函数。

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}

We start with the move function (which I cleaned up a little bit):

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

Let's start with the easier part - that is, when the function is called with rvalue:

Object a = std::move(Object());
// Object() is temporary, which is prvalue

and our move template gets instantiated as follows:

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

Since remove_reference converts T& to T or T&& to T, and Object is not reference, our final function is:

Object&& move(Object&& arg)
{
  return static_cast<Object&&>(arg);
}

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).


Here's what happens when we call move with lvalue:

Object a; // a is lvalue
Object b = std::move(a);

and corresponding move instantiation:

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

Again, remove_reference converts Object& to Object and we get:

Object&& move(Object& && arg)
{
  return static_cast<Object&&>(arg);
}

Now we get to the tricky part: what does Object& && even mean and how can it bind to lvalue?

To allow perfect forwarding, C++11 standard provides special rules for reference collapsing, which are as follows:

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

As you can see, under these rules Object& && actually means Object&, which is plain lvalue reference that allows binding lvalues.

Final function is thus:

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

which is not unlike the previous instantiation with rvalue - they both cast its argument to rvalue reference and then return it. The difference is that first instantiation can be used with rvalues only, while the second one works with lvalues.


To explain why do we need remove_reference a bit more, let's try this function

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

and instantiate it with lvalue.

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

Applying the reference collapsing rules mentioned above, you can see we get function that is unusable as move (to put it simply, you call it with lvalue, you get lvalue back). If anything, this function is the identity function.

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}
り繁华旳梦境 2024-12-12 14:56:18

_Ty 是模板参数,在这种情况下

Object obj1;
Object obj2 = std::move(obj1);

_Ty 是类型“Object &”

这就是为什么 _Remove_reference 是必要的。

But

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

如果我们没有删除引用,它会更像我们正在做的那样

Object&& obj2 = (ObjectRef&&)obj1_ref;

ObjectRef&&简化为 Object &,我们无法将其绑定到 obj2。

之所以减少这种方式,是为了支持完美转发。请参阅本文

_Ty is a template parameter, and in this situation

Object obj1;
Object obj2 = std::move(obj1);

_Ty is type "Object &"

which is why the _Remove_reference is necessary.

It would be more like

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

If we didn't remove the reference it would be like we were doing

Object&& obj2 = (ObjectRef&&)obj1_ref;

But ObjectRef&& reduces to Object &, which we couldn't bind to obj2.

The reason it reduces this way is to support perfect forwarding. See this paper.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文