2

While learning about std::exchange on cppreference.com , I came across its "possible implementation" which is pasted below.

  template<class T, class U = T>
  T exchange(T& obj, U&& new_value)
  {
      T old_value = std::move(obj);
      obj = std::forward<U>(new_value);
      return old_value;
  }

It looked a bit odd to see obj = std::forward in an assignment statement instead instead of std::move, which I think would have the same effect.

My understanding is that std::move is equivalent to a static_cast to rvalue reference type (but more expressive) and returns an xvalue always, whereas std::forward returns the same value type it is passed.

My question is why is std:forward used in above snippet? and is there an easy rule of thumb to decide when this is the better choice?

bolov
  • 72,283
  • 15
  • 145
  • 224
rakeshdn
  • 135
  • 1
  • 7

1 Answers1

5

The fact that there's assignment is irrelevant. What distinguishes forward from move is what kind of argument they take when used inside your function:

  • The argument of forward is a forwarding reference parameter of your function.
  • The argument of move is an rvalue reference parameter of your function (or more generally, anything you know to have no further aliases).

If you exchange with an lvalue, you don't want that to be moved-from!

For example, consider:

std::vector<int> a, b;

f(std::exchange(a, b));

This effectively calls f(a) but also performs a = b;. It does not perform a = std::move(b); b is still usable in its original form afterwards!

See also this answer of mine, as well as many other related answers like What is std::move(), and when should it be used?, What's the difference between std::move and std::forward, and links therein.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    typo: move takes an lvalue reference. it casts *to* an rvalue reference. – Richard Hodges Jan 26 '19 at 05:49
  • Thanks for answer and the links. So the main thing is we do not want to forcibly turn an lvalue reference to an rvalue inside the function and potentially have it invalidated. The code I was looking at the time was just passing { } as the second argument, but as you say the difference is when b is an lvalue. – rakeshdn Jan 26 '19 at 11:08
  • @RichardHodges: No, I meant what I wrote, but I said it poorly. I will adjust it, thanks. (Typical arguments of both forward and move are always lvalues, of course, since they are typically id-expressions.) – Kerrek SB Jan 26 '19 at 20:24