Skip to content

Instantly share code, notes, and snippets.

@MangaD
Created August 2, 2026 15:11
Show Gist options
  • Select an option

  • Save MangaD/b0e698909d3a9a0e5fdacea24777708a to your computer and use it in GitHub Desktop.

Select an option

Save MangaD/b0e698909d3a9a0e5fdacea24777708a to your computer and use it in GitHub Desktop.
How `std::move` Creates an Rvalue Reference in C++

How std::move Creates an Rvalue Reference in C++

CC0

Disclaimer: ChatGPT generated document.

The key point is that std::move does not actually move anything, and it doesn't create a new object. It simply casts an expression to an rvalue reference, allowing move constructors or move assignment operators to be selected.

A simplified implementation looks like this:

template <typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(t);
}

Let's break that down.

Step 1: std::move accepts anything

The parameter is a forwarding reference:

T&& t

Suppose you have

std::string s = "hello";

and call

std::move(s);

Template deduction gives

T = std::string&

because s is an lvalue.

So the parameter type becomes

std::string& &&   // collapses to std::string&

Inside the function, t is simply a reference to s.


Step 2: Remove any reference qualifiers

std::remove_reference_t<T> turns

std::string&

into

std::string

Now we have

std::string&&

Step 3: Perform a cast

The return statement is

static_cast<std::string&&>(t)

This tells the compiler:

"Treat this object as an xvalue (an expiring value)."

No object is created. No bytes are copied. The object is still s.


Example

std::string s = "hello";

std::string t = std::move(s);

Conceptually, this becomes

std::string t = static_cast<std::string&&>(s);

Because the initializer is now an rvalue, overload resolution prefers the move constructor:

std::string(std::string&&);

instead of

std::string(const std::string&);

Why does this work?

The C++ language classifies expressions into value categories:

  • lvalue — has identity (s)
  • prvalue — temporary (std::string("hello"))
  • xvalue — expiring object (std::move(s))

static_cast<T&&>(obj) changes the expression's value category to an xvalue.

So after

auto&& x = std::move(s);

x refers to the same object as s, but the expression std::move(s) is an xvalue, making move overloads eligible.


An important subtlety

Although std::move(s) has type std::string&&, if you bind it to a named variable:

auto&& r = std::move(s);

then r itself is an lvalue expression, because every named variable is an lvalue.

So:

foo(r);              // calls lvalue overload
foo(std::move(r));   // calls rvalue overload

This often surprises people.


Summary

std::move is essentially just:

template <typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(t);
}

It:

  1. Accepts any object by reference.
  2. Removes any existing reference qualifiers.
  3. Casts the expression to an rvalue reference using static_cast<T&&>.

It does not move data itself. The actual move occurs only if a move constructor or move assignment operator is invoked on the resulting xvalue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment