Question
What T&& Means in C++11: Rvalue References Explained
Question
I have been exploring the new features introduced in C++11, and I noticed declarations such as T&& var.
What is this syntax called?
What does it mean exactly?
At first glance, it looks like a “double reference,” similar to how C-style code can use double pointers like T** var, but I am struggling to understand its purpose and when it would be used.
Short Answer
By the end of this page, you will understand that T&& in C++11 usually means an rvalue reference. You will learn what rvalues are, why rvalue references were added, how they enable move semantics and perfect forwarding, and how they differ from ordinary references like T& and const T&.
Concept
In C++11, T&& is called an rvalue reference.
To understand it, you first need to know that C++ expressions are often divided into two broad categories:
- lvalues: named objects that have a stable location in memory
- rvalues: temporary values, literals, or objects about to expire
For example:
int x = 10; // x is an lvalue
int y = x; // x is still an lvalue
int z = 5; // 5 is an rvalue
Before C++11, C++ had normal references like T& and const T&:
T&binds to modifiable lvaluesconst T&can bind to both lvalues and rvalues
C++11 added T&& so code can specifically bind to rvalues.
This matters because rvalues are often temporary objects that will not be used again. Since they are about to disappear, C++ can safely move their resources instead of copying them.
That is the main reason T&& exists:
- to support move semantics
- to avoid unnecessary copies
- to improve performance
- to enable perfect forwarding in templates
A classic example is a std::string. Copying a large string may require allocating memory and copying characters. Moving it can simply transfer ownership of its internal buffer.
std::string a = "hello";
std::string b = a; // copy
std::string c = std::move(a); // move
In the move case, std::move(a) turns a into an rvalue-like expression, which lets c take over its internal data.
So T&& is not a “reference to a reference” in the everyday sense. It is a distinct language feature for binding to temporary or expiring objects.
Mental Model
Think of C++ objects like physical containers.
- An lvalue is a labeled storage box sitting on a shelf. It has a name and a location.
- An rvalue is a temporary package that just arrived and has not been put on a shelf.
A normal reference T& is like saying, “Give me access to that named box on the shelf.”
An rvalue reference T&& is like saying, “This temporary package is about to be discarded anyway, so I am allowed to take its contents instead of making a full copy.”
That is why T&& is so useful for performance. If the object is temporary, C++ can often reuse its internals rather than duplicate them.
Syntax and Examples
Basic syntax
T&& name = expression;
This usually means name is an rvalue reference, and expression must be an rvalue.
Simple example
int&& a = 5;
This works because 5 is an rvalue.
But this does not work:
int x = 5;
int&& a = x; // error: x is an lvalue
Even though x stores a value, the expression x itself is an lvalue because it has a name.
Using std::move
#include <iostream>
#include <string>
#include <utility>
int main() {
std::string s1 = "hello";
std::string s2 = std::move(s1);
std::cout << "s2 = " << s2 << '\n';
}
Explanation
s1is an lvalue because it has a name.std::move(s1)converts it to an rvalue expression.- That allows move construction of
s2.
Function overloads with T& and T&&
#include <iostream>
void process(int& x) {
std::cout << "lvalue reference\n";
}
void process(int&& x) {
std::cout << "rvalue reference\n";
}
int main() {
int a = 10;
process(a); // calls int& version
process(20); // calls int&& version
}
This is one of the clearest ways to see the difference.
ais an lvalue20is an rvalue
Step by Step Execution
Consider this example:
#include <iostream>
#include <utility>
#include <string>
void print(std::string& s) {
std::cout << "lvalue: " << s << '\n';
}
void print(std::string&& s) {
std::cout << "rvalue: " << s << '\n';
}
int main() {
std::string name = "Alice";
print(name);
print(std::string("Bob"));
print(std::move(name));
}
Step-by-step
-
std::string name = "Alice";- Creates a named string object.
nameis an lvalue.
-
print(name);- Since
nameis an lvalue, the compiler chooses: -
void print(std::string& s)
- Since
-
print(std::string("Bob"));std::string("Bob")creates a temporary object.- Temporary objects are rvalues.
- The compiler chooses:
-
void print(std::string&& s)
-
print(std::move(name));nameis normally an lvalue.std::move(name)converts it into an rvalue expression.- The compiler chooses the
std::string&&overload.
Important detail
Inside the function, even a parameter declared as T&& has a name, so the parameter itself is an lvalue when used in expressions.
That is why move constructors and forwarding code often use std::move(param) again inside the function body.
Real World Use Cases
Rvalue references appear in many real C++ programs.
1. Move constructors and move assignment
Classes that manage resources such as memory, file handles, sockets, or buffers often define move operations:
class Buffer {
public:
Buffer(Buffer&& other);
Buffer& operator=(Buffer&& other);
};
This allows efficient transfer of ownership instead of expensive copying.
2. Standard library containers
Containers such as std::vector use move semantics when growing or rearranging elements. This can make operations much faster for movable types.
3. Returning objects from functions
Functions often return objects by value:
std::string make_message() {
return "hello";
}
C++ can move the returned object efficiently when needed.
4. Perfect forwarding in generic code
Template utilities such as std::make_unique and emplace_back rely on T&& in a special template context to forward arguments efficiently.
5. Avoiding copies in APIs
Functions that consume ownership may accept rvalue references:
void set_name(std::string&& name);
This signals that the function may move from the argument.
Real Codebase Usage
In real projects, developers usually use T&& in a few important patterns.
Move constructors
class FileWrapper {
public:
FileWrapper(FileWrapper&& other) noexcept {
handle = other.handle;
other.handle = nullptr;
}
private:
void* handle = nullptr;
};
This pattern transfers ownership and leaves the source object in a valid but moved-from state.
Move assignment
class FileWrapper {
public:
FileWrapper& operator=(FileWrapper&& other) noexcept {
if (this != &other) {
handle = other.handle;
other.handle = nullptr;
}
return *this;
}
private:
void* handle = nullptr;
};
Overloads for performance
void set_value(const std::string& s); // copy or read
void set_value(std::string&& s); // move
This is common in classes that store strings, vectors, or other expensive objects.
Perfect forwarding
template <typename T>
void wrapper(T&& value) {
process(std::forward<T>(value));
}
Here, T&& is special. In a template with type deduction, this can become a forwarding reference.
- if passed an lvalue, it behaves like an lvalue reference
- if passed an rvalue, it behaves like an rvalue reference
This is heavily used in generic libraries.
Guarding moved-from objects
After moving from an object, code should still leave it valid. Real code often resets pointers, clears handles, or sets safe default values.
Common Mistakes
1. Thinking T&& means “reference to reference”
It does not mean the same thing as a double pointer like T**.
T&& is a language feature for rvalue references.
2. Assuming a named T&& variable is still an rvalue
This is one of the most common mistakes.
void f(std::string&& s) {
g(s); // s is an lvalue here because it has a name
}
If you want to pass it onward as an rvalue, use:
void f(std::string&& s) {
g(std::move(s));
}
3. Using std::move too early
std::string name = "Alice";
std::string other = std::move(name);
std::cout << name; // valid, but value is unspecified
A moved-from object is valid, but you should not assume it still contains its old value.
4. Trying to bind T&& directly to an lvalue
Broken code:
int x = 10;
int&& r = x; // error
Correct version:
int x = 10;
int&& r = std::move(x);
5. Forgetting std::forward in forwarding code
Broken pattern:
template <typename T>
void wrapper(T&& value) {
process(value); // always passes as lvalue
}
Correct pattern:
template <typename T>
void wrapper(T&& value) {
process(std::forward<T>(value));
}
6. Writing move operations when the defaults are enough
Sometimes the compiler-generated move constructor and move assignment operator are already correct. Writing custom move code when it is unnecessary can introduce bugs.
Comparisons
| Concept | Binds to lvalues? | Binds to rvalues? | Typical use |
|---|---|---|---|
T& | Yes | No | Modify a named object |
const T& | Yes | Yes | Read without copying |
T&& | No, not directly | Yes | Move from temporaries or expiring objects |
T& vs T&&
int x = 1;
int& a = x; // OK
int&& b = 2; // OK
int&& c = x; // Error
T&is for normal named objects.T&&is for temporary or explicitly moved objects.
const T& vs T&&
const T& can bind to temporaries too:
const std::string& a = std::string("hello");
So why use T&&?
const T&lets you observe a temporaryT&&lets you modify or move from it
Rvalue reference vs forwarding reference
template <typename T>
void f(T&& x);
This looks like an rvalue reference, but when T is deduced, it is a forwarding reference.
That means it can bind to both lvalues and rvalues depending on the call.
By contrast:
void f(std::string&& x);
This is always a plain rvalue reference.
Cheat Sheet
Quick definition
T&&usually means rvalue reference- Used for move semantics and perfect forwarding
Value categories
- lvalue: named object, stable identity
- rvalue: temporary value or expiring object
Binding rules
T& -> binds to non-const lvalues
const T& -> binds to lvalues and rvalues
T&& -> binds to rvalues
Examples
int x = 10;
int& a = x; // OK
const int& b = 5; // OK
int&& c = 5; // OK
int&& d = x; // Error
int&& e = std::move(x); // OK
Important rule
A named rvalue reference is an lvalue in expressions:
void f(std::string&& s) {
use(s); // s is lvalue here
use(std::move(s)); // now treated as rvalue
}
Common tools
std::move(x)casts to an rvalue expressionstd::forward<T>(x)preserves lvalue/rvalue category in templates
Typical uses
- move constructor
- move assignment operator
- overloads for expensive objects
- perfect forwarding in templates
Safe expectation after move
- moved-from object is valid
- its exact content is usually unspecified
FAQ
What is T&& called in C++?
It is usually called an rvalue reference.
Is T&& a double reference?
No. It is not like a double pointer such as T**. It is a separate C++ language feature.
Why was T&& added to C++11?
It was added mainly to support move semantics and perfect forwarding, which improve performance and flexibility.
Can T&& bind to a normal variable?
Not directly. A named variable is usually an lvalue. You would need std::move(variable) to treat it as an rvalue.
What is the difference between const T& and T&&?
const T& can read from both lvalues and rvalues without copying. T&& is used when you want to move from or modify an expiring object.
Why is a named T&& parameter treated as an lvalue inside a function?
Because any named variable is an lvalue expression, regardless of its type.
What is std::move actually doing?
It does not move by itself. It converts an expression into an rvalue so move operations can be selected.
Is T&& always an rvalue reference in templates?
No. In a deduced template context like template<typename T> void f(T&& x), it becomes a forwarding reference.
Mini Project
Description
Build a small class that manages a dynamic array and supports both copying and moving. This project demonstrates why rvalue references matter: copying duplicates data, while moving transfers ownership efficiently.
Goal
Create a resource-owning class with a move constructor and move assignment operator, then test the difference between copy and move behavior.
Requirements
- Create a class that allocates an array with
new[]and frees it in the destructor. - Add a copy constructor that performs a deep copy.
- Add a move constructor that transfers the pointer and resets the source object.
- Add a move assignment operator that safely transfers ownership.
- Write a
main()function that shows copy and move operations in action.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.