std::move vs std::forward

The std::move and std::forward are used to serve two different purposes. The std::move enables move semantic for efficient resource transfer from one object to another. Whereas, the std::forward is used to preserve the value category (lvalue or rvalue) of the parent function’s template parameter in time of calling another function. This feature is also known as perfect forwarding.

References:

std::move and std::forward are often confused because of their uncanny similarities.

The std::move(t) is equivalent to:

 static_cast<typename std::remove_reference<T>::type&&>(t)

And std::forward<T>(t) is equivalent to:

static_cast<T&&>(t)

Both apparently try to cast the input object to rvalue reference type. This is not entirely true. There is a subtle difference which we’ll try to understand in the following sections.

#include <iostream>

void equivalent(int& a) {
    std::cout << "int&: " << a << std::endl;
}

void equivalent(int&& a) {
    std::cout << "int&&: " << a << std::endl;
}

template <typename T>
void wrapper1(T&& a) {
    equivalent(std::move(a));
}

template <typename T>
void wrapper2(T&& a) {
    equivalent(std::forward<T>(a));
}

int main() {
    int x = 10;

    std::cout << "Calling wrapper1 with std::move()..." << std::endl;
    wrapper1(x);
    wrapper1(30);

    std::cout << "Calling wrapper2 with std::forward()..." << std::endl;
    wrapper2(x);
    wrapper2(30);

    return 0;
}

Consider this example. The wrapper1() and wrapper2() are two similar functions expect std::move() is used in the first one and std::forward() is used in the second one.

Note that this is not the way std::move() is usually used. This example is only to understand the difference between std::move and std::forward.

Now look at the output.

$ g++ -o test test.cpp 
$ ./test 
Calling wrapper1 with std::move()...
int&&: 10
int&&: 30
Calling wrapper2 with std::forward()...
int&: 10
int&&: 30

Both wrapper1(x) and wrapper1(30) calls eventually were translated into the equivalent(int&& a) call. But wrapper2(x) was translated into equivalent(int& a) and wrapper2(30) into equivalent(int&& a). Clearly std::move and std::forward induced different behaviors in their respective wrapper function. So they are inherently different.

The std::move always casts the input object into an rvalue reference irrespective of the value category of the object. That’s why the equivalent(int&& a) was called in both cases. In fact, the variable ‘a’ is always an lvalue inside the wrapper function even though it type could be of type lvalue reference or rvalue reference.

The std::forward does not always cast the object into an rvalue reference. Some times it casts the input object into rvalue reference and sometime into lvalue referece depending on the value category of the container function’s (wrapper2()) input function (a).

The std::forward is usually used inside a function that has an input argument of type universal (forwarding) reference. And that argument needs to be passed to another function preserving the value category.

The std::forward – perfect forwarding article described how it works in detail.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

One thought on “std::move vs std::forward”

  1. Hello, you have an error in url link at the bottom of article.
    “std::forward – perfect forwarding” – there is no link there.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
0