1. What is “Move Semantics”

In C++11 there is a new feature which named “Move Semantics”. It offers an ability that we don’t actually copy an object instead we move it. When initializing a new object through another, it will be no necessary to make a copy. Instead, the new object can acquire the resources that held by the old one, which may leads a highly improvement of performance. In order to support move semantics, C++11 introduced a new reference type, rvalue reference (rvalue means “right value”, it distinguished from the original lvalue reference). rvalue reference is used to bind to an object which we think it’s “dying”. the value of a “dying” object will not be used in the future. A move constructor of type T has the parameter which is T’s rvalue reference. That is, we can implement the move constructor to let it “move” but not copy.

class Moveable {

  // This is the resource which held by class Moveable.
  Resource* r;

 public:
  Moveable(const Moveable& m) {
    std::cout << "copy\n";
    // This is copy constructor. We copy the value from m.r to this->r.
    this->r = new Resource(m.r);
  }
  Moveable(Moveable&& m) {
    std::cout << "move\n";
    // This is move constructor. We make this->r acquires the value from m.r, and m.r will become nullptr.
    this->r = m.r;
    m.r = nullptr;
  }
  Moveable& operator=(const Moveable& m) {
    std::cout << "copy\n";
    // This is copy assigner. Do the similar as the copy constructor.
    return *this;
  }
  Moveable& operator=(Moveable&& m) {
    std::cout << "move\n";
    // This is move assigner. Do the similar as the move constructor.
    return *this;
  }
};

A rvalue reference is in form of T&& (lvalue reference is T&). A rvalue reference can only bind to an rvalue but unable to bind to an lvalue. Consider the case below.

void foo(std::string&& v) {
  // ...
}

The invoking such as foo("1") and foo(str + "1") is valid. but foo(str) isn’t allowed, because it tries to bind an rvalue reference to str, which is an lvalue. Now we have problem. Assume the string str will no longer be used after invokes foo(str). apparently str can be treated as “dying”, we don’t want have an extra copy, so we hope it can be cast to an rvalue. So that’s why we need std::move.

2. Universal Reference

Before we introduce std::move, let’s see a interesting trick.

template <typename T> void func(T&& v) {}
std::string str = "123";
func(str); // OK
func(str + "4"); // OK

In order to understand these codes, we should first learn about template deduction. When we pass str to func, complier notices that it is an lvalue, so it deduces the template parameter T as an lvalue reference type. The argument T&& v then seems become an rvalue reference to an lvalue reference of std::string. Wait, we all know there is no a reference to a reference, but here are special rules called “Reference Collapsing”. If we use template or type alias to try to create “a reference to a reference”, then it will be collapsed to an ordinary lvalue or rvalue reference.

There are 4 rules of reference collapsing, which listed below. We let TR denote the reference type to T, meanwhile T is the reference type to A. The collapsing will result a reference type to A.

TR T Result
T& A& A&
T&& A& A&
T& A&& A&
T&& A&& A&&

Besides the rvalue reference to rvalue reference, the rest of three are all result to an lvalue reference. Thus, when invoking func(str), T is std::string& and T&& results std::string&, so argument v is an lvalue reference.

In such a template, the argument can either be lvalue reference or rvalue reference. We call this technique as “Universal Reference”. With this technique, in addition to using const lvalue reference as argument, we find another way to create a call-by-reference function which can accepts both lvalue and rvalue.

For avoiding confusing, we must point out that a universal reference template will instantiate two different definitions to accept lvalue and rvalue, respectively. So when we invoke with lvalue and rvalue respectively, we are actually invoking two different functions. This is how C++’s template instantiation doing.

3. What is std::move and How it Works

Now let’s move on to the std::move, and see a simple case firstly. We implemented a func MyMove, but it’s a bad implementation compares to the std::move. Recall the the effect of std::move is casting to rvalue.

template <typename T>
T&& MyMove(const T& v) {
  return static_cast<T&&>(const_cast<T&>(v));
}

void Call(Moveable&& m) {
  std::cout << "move\n";
};
void Call(const Moveable&& m) {
  std::cout << "const move\n";
};
void Call(Moveable& m) {
  std::cout << "copy\n";
}

const Moveable m;
Call(MyMove(m));      // Print "move".
Call(std::move(m)); // Print "const move"

We’ve noticed that MyMove will lose the const qualified. Now let’s see one of the implementations of std::move in standard library.

template <typename _Tp>
typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept {
  return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);
}

The argument __t has type of _Tp&&, which _Tp is template parameter. This is a universal reference so it can accepts both lvalue and rvalue. We don’t need to know the details of std::remove_reference at present. Just remember if there is a reference type, then it results a dereference type. So typename std::remove_reference<_Tp>::type&& is an rvalue reference.

FYI. We paste the implementation of std::remove_reference<_Tp>::type below. Feel free to explore it if you take any interest.

template <typename _Tp>
struct remove_reference {
  typedef _Tp type;
};

template <typename _Tp>
struct remove_reference<_Tp&> {
  typedef _Tp type;
};

template <typename _Tp>
struct remove_reference<_Tp&&> {
  typedef _Tp type;
};

4. What is std::forward and When Should We Use it

There is a truth that might be counterintuitive is that an rvalue reference itself is an lvalue. It’s confusing for beginners, let’s just see a case below.

void Call(int&& m) { std::cout << "move\n"; };
void Call(int& m) { std::cout << "copy\n"; }

int&& r = 0;
Call(r);            // Print "copy"
Call(std::move(r)); // Print "move"

r is an rvalue reference. If we want to invoke the overload of Call whose argument is rvalue reference, just use std::move. However, consider a situation of universal references, we can’t figure out whether the argument is lvalue reference or rvalue reference. For example.

template<typename T1, typename T2>
void CallTwo(T1&& arg1, T2&& arg2) {
  Call(arg1);
  Call(arg2);
}

int v = 123;
CallTwo(v, 123); // arg1 is an lvalue reference, arg2 is an rvalue reference.
CallTwo(123, v); // arg1 is an rvalue reference, arg2 is an lvalue reference.

What we want to do is invoking Call(int&) for lvalue reference, and invoking Call(int&&) for rvalue reference. For the codes above, CallTwo can invoke Call(int&) only. If we use std::move then it will only call Call(int&&) instead. Is there any universal way to cast rvalue references to rvalues but keep lvalue references as lvalues? Of course we have std::forward!

Let’s use std::forward to update above codes.

void Call(int&& m) { std::cout << "move\n"; };
void Call(int& m) { std::cout << "copy\n"; }

template<typename T1, typename T2>
void CallTwo(T1&& arg1, T2&& arg2) {
  Call(std::forward<T1>(arg1));
  Call(std::forward<T2>(arg2));
}

int v = 123;
CallTwo(v, 123); // Print "copy\nmove\n"
CallTwo(123, v); // Print "move\ncopy\n"

Now let’s continue to explore the details of std::forward.

/**
 *  @brief  Forward an lvalue.
 *  @return The parameter cast to the specified type.
 *
 *  This function is used to implement "perfect forwarding".
 */
template <typename _Tp>
_Tp&& forward(
    typename std::remove_reference<_Tp>::type& __t) noexcept {
  return static_cast<_Tp&&>(__t);
}

/**
 *  @brief  Forward an rvalue.
 *  @return The parameter cast to the specified type.
 *
 *  This function is used to implement "perfect forwarding".
 */
template <typename _Tp>
_Tp&& forward(
    typename std::remove_reference<_Tp>::type&& __t) noexcept {
  static_assert(!std::is_lvalue_reference<_Tp>::value,
                "template argument"
                " substituting _Tp is an lvalue reference type");
  return static_cast<_Tp&&>(__t);
}

There are two overloads of std::forward. One for forwarding an lvalue and another for forwarding an rvalue. How the compiler distinguish between these two overloads? In fact the complier can not deduce the type of template parameter _Tp. This why we should call std::forward<T1>(arg1) instead of std::forward(arg1). The key is that we explicitly specifying the template parameter. With properly specifying the template parameter, it can be ensured that the first overload accepts lvalue (recall that both lvalue reference and rvalue reference are lvalues.) and the second overload accepts rvalue.

Do you still remember when a universal reference is exactly an lvalue reference, the template parameter is actually an lvalue reference type? Here, if arg1 has type of int& then T1 is int&. Otherwise if arg1 has type of int&& then T1 is int. typename std::remove_reference<_Tp>::type& will always result an lvalue reference, meanwhile _Tp can either be an lvalue reference type or a non reference type. When the passed parameter is an lvalue reference, _Tp is an lvalue reference type which results return type _Tp&& is lvalue reference. Alternatively, when the passed parameter is an rvalue reference, _Tp is a non reference type which results return type _Tp&& is rvalue reference.

So the invokes in CallTwo always match the first overload. Sometimes we need to pass an rvalue to std::forward, e.g., using std::forward in a macro and the macro arguments is std::move(...) or literal. In this case, the matched overload will be the second one. Since the second overload always accepts rvalue, its return type should always be rvalue reference, too. So we have a static_assert here to ensure the _Tp is not an lvalue reference type. The invoking such as std::forward<int&>(123) should be fail to compile.