What Lies Behind the Double Ampersand in C++11? Breaking Down T&&
\n\nIf you've ever written C++ and looked at code written to the C++11 standard or newer, you've likely encountered a strange syntax: T&&. The double ampersand immediately catches your eye — after all, in the good old C++98/03, we were used to & meaning a reference. And here there are two of them! What is this? A reference to a reference? A compiler error? Or something much cooler?
Spoiler: it's not an error. It's one of the most powerful features of modern C++, which revolutionized the understanding of performance and resource management. In this article, we'll figure out what T&& means, how it differs from a regular reference, and how to use it correctly so your code flies, not crawls.
We'll answer real questions from Stack Overflow that torment thousands of developers, and show live code examples. Let's go!
\n\n1. Introduction to Rvalue References: Why One Ampersand Isn't Enough?
\n\nFirst, let's recall what lvalues and rvalues are. In short:
\n\n- \n
- lvalue (left value) — is an object that has a name and an address in memory. Example:
int x = 5;—xis an lvalue. \n - rvalue (right value) — is a temporary value that has no name and is usually destroyed after the expression. Example:
5,x + 2, the result of a function callgetTemp(). \n
In C++03, we had only one reference — int& (lvalue reference). It could only bind to lvalues. Try passing a temporary object to a function expecting int&, and the compiler will give an error. This is protection: modifying a temporary object is a bad idea.
But what if we want to move data from a temporary object instead of copying it? For example, we have a std::string returned from a function. Copying it is expensive. But "stealing" its internal buffer is cheap. This is exactly what rvalue references (T&&) are for.
// Function that takes an rvalue reference\nvoid process(std::string&& str) {\n std::cout << "Processing temporary string: " << str << std::endl;\n // Here you can move the data instead of copying\n}\n\nint main() {\n std::string s = "Hello";\n process(std::move(s)); // std::move turns an lvalue into an rvalue\n // process(s); // Error! s is an lvalue, but the function expects an rvalue\n return 0;\n}\n\nNow T&& is not a reference to a reference, but a new type of reference that can only bind to rvalues. This is the answer to the main question: T&& in C++11 means an rvalue reference.
2. Move Semantics: How T&& Speeds Up Code
\n\nThe main reason for the introduction of rvalue references is move semantics. Imagine you have a vector of 10,000 elements. In the old version of C++, when returned from a function, it was copied:
\n\nstd::vector<int> createBigVector() {\n std::vector<int> v(10000, 42);\n return v; // Copying in C++03, moving in C++11\n}\n\nIn C++11, the compiler sees that v is an lvalue, but it's smart: it knows that v will soon die, so it applies implicit move. But if you want to explicitly move data, use std::move.
Consider a class with dynamic memory:
\n\nclass MyBuffer {\nprivate:\n int* data;\n size_t size;\npublic:\n // Move constructor\n MyBuffer(MyBuffer&& other) noexcept\n : data(other.data), size(other.size) {\n other.data = nullptr; // Important! Leave the source in a valid state\n other.size = 0;\n }\n \n // Regular constructor