Published on

R-value reference, std::move and perfect forwarding


What are R-value references, std::move, and std::forward?

Once you've got a good handle on C++, some things can get pretty puzzling, and one of those things is R-value references. But understanding them is key to getting your head around move semantics and perfect forwarding.

So, first off, let's clear up what we mean by r-values and l-values. If something has a spot in the computer's memory, it's called an l-value. Anything else is an r-value. For instance:

int a = 7; // a is an l-value

int b = int(2); // Here, int(2) is an r-value

R-value references

R-value references are a bit different from r-values. Since they're references, they've got a home in memory. This matters because normal temporary (r-value) things can't connect to l-value references. To fix this, we use r-value references. Check it out:

void someFcn(int& w){

someFcn(2); //  Oops! Can't connect an r-value to an l-value reference!

But with an r-value reference, it's all good:

void someFcn(int&& w){

someFcn(2); //  All good now!

Here's another important thing to remember:

Even though a parameter is connected to an r-value reference, it's still an l-value.

In that example above, w is the parameter, which is always an l-value, even though it's connected to an r-value reference. How do we know? Well, if you peek inside someFcn while it's running, you'll see you can find the address of w. That means it's an l-value.


Once you've wrapped your head around all that, understanding std::move is pretty straightforward. Basically, it doesn't physically move anything.

It just changes a regular reference into an r-value reference.

Here's a simple way to make a move function yourself:

template<typename T>
decltype(auto) move(T&& arg){
    return static_cast<T&&>(arg);

It's just changing a reference to an r-value reference. It doesn't move anything physically. We use remove_reference_t because arg is a fancy kind of reference that can connect to both r-values and l-values. But what's that fancy reference called? Well, it's called a universal reference. But don't worry about that too much for now. Just think of it as a reference that can connect to both kinds of values and looks like this:

template<typename T>
any_return_type fcn(T&& I_Am_A_Universal_Reference)

std::move doesn't always move

So you've used std::move and you think your code's super efficient now, right? Nope, not quite. Check this out:

class Widget{
    explicit Widget(const std::string uniqueID): uuid(std::move(uniqueID)){

    std::string uuid;

This code seems fine, but it actually makes a copy, not a move. That's because uniqueID is an l-value since it's a parameter. So even when we move it, the reference changes but the const tag sticks around. That means after moving, uniqueID is still treated as a constant r-value. But the move constructor we want to use needs its parameter to be an r-value, so it ends up using the copy constructor instead.

  1. So, if you want to move things, don't declare them as const.
  2. If you do, move turns into copy.


Think of std::move as a simple cast that always turns things into r-values. But std::forward is a bit smarter. Here's how it works:

  1. If the input parameter is an r-value reference, it casts it to an r-value.
  2. If the input parameter is an l-value reference, it keeps it as an l-value.
  3. Remember, the parameter itself is always an l-value.

Here's an example to make it clearer:

void print(const std::string& lValueArg);
void print(std::string&& rValueArg);

template<typename T>
void fcn(T&& param){

Calls fcn with an R-value reference. std::forward sees this and turns the parameter, which is originally an l-value, into an r-value reference. Then it sends it to print, and it uses this definition:
void print(std::string&& rValueArg);
fcn("Bound to R-value reference");

Calls fcn with an L-value reference. std::forward sees this and doesn't do anything (it's a conditional cast), then sends it to print, and it uses this definition:
void print(const std::string& lValueArg);
std::string lVal = "Bound to L-value reference";


  1. std::move changes things into r-values without conditions.
  2. std::forward only changes things into r-values if they're originally r-values.
  3. Neither of them does anything fancy at runtime.