C++ Part I · Module 13 · Copy and Move Semantics
Rvalue References
Chapter 13.6 established how C++ classifies an expression by whether anything still owns the object it names, and left the classification unused. Nothing written so far can act on it, because none of the parameter forms available is selective in the right way: a const reference parameter accepts an rvalue happily and then promises not to modify it, which rules out taking its resource, and a plain non-const reference parameter refuses rvalues outright. What a function needs in order to offer a cheaper path for expiring objects is a third form that binds exclusively to the expressions nobody owns, so that an overload taking it is chosen precisely when stealing is safe and never otherwise. This chapter is that form. Its syntax is two ampersands, its binding behaviour is worth measuring rather than assuming because two of the nine cases refuse things readers commonly expect them to accept, and it carries one hazard that follows directly from the previous chapter: the parameter it declares is a named variable, and naming something makes an expression an lvalue, so a function that takes an rvalue reference and then passes the parameter onward by name has quietly turned the move it was trying to enable back into a copy.
Advanced17 min readC++Part IFundamentalsValue Categories
Part I · Module 13 · Chapter 13.7 · Rvalue References
1. The Form
void f(std::string&& s); // an rvalue reference parameterTwo ampersands. It is a reference — Chapter 8.5's material, an alias for an existing object with no storage of its own — that binds only to rvalues.
Read it as: "a reference to something nobody will use again."
2. Why the Existing Forms Are Not Enough
Chapter 8.6 compared pointers and references and Chapter 6.5 established const T& as the default parameter form. Neither helps here:
const T& accepts an rvalue and then forbids modifying it. It binds to everything, which sounds ideal and is exactly wrong for this purpose — a function cannot take an object's resource through a reference that promises not to change it. Chapter 11.7 §4 measured const propagating into members, which is the mechanism.
T& refuses rvalues. So a function taking T& cannot be offered a temporary at all.
What is needed is a form that binds only to rvalues, so that overload resolution picks it exactly when the argument is expiring.
3. The Binding Table
Measured, nine cases compiled separately, with std::string s; const std::string cs; std::string makeS();
| Parameter / initializer | s (lvalue) | makeS() (rvalue) | cs (const lvalue) |
|---|---|---|---|
T& | binds | refused | refused |
const T& | binds | binds | binds |
T&& | refused | binds | refused |
The exact messages, because they are informative:
T& makeS() error: non-const lvalue reference to type 'basic_string<...>'
cannot bind to a temporary
T& cs error: binding reference of type 'basic_string<...>' to value of type
'const basic_string<...>' drops 'const' qualifier
T&& s error: rvalue reference to type 'basic_string<...>' cannot bind to
lvalue of type 'basic_string<...>'
T&& cs error: binding reference of type ... drops 'const' qualifierAnd T&& r = std::move(s); binds — because std::move(s) is an rvalue expression (Chapter 13.6 §4), even though s is not.
4. Reading the Table
const T& is the only form that binds to everything. That is why Chapter 6.5 made it the default: it accepts any argument. It is also why it cannot be the move parameter — accepting everything means it cannot distinguish the case where stealing is safe.
T&& is the only form that binds to nothing a caller still owns. That is the entire feature. A function overloaded on T&& is selected if and only if the argument is expiring, which means a body that takes the argument's resource cannot be reached with an argument somebody is still using.
The guarantee is not that the body is careful. It is that the overload is unreachable from an lvalue.
And the two refusals surprise people.
T&& r = s; is refused even though s is a non-const, modifiable object you clearly own. The point is precisely that you own it — binding an rvalue reference to it would advertise it as expiring when it is not.
T&& r = cs; is refused for a different reason — dropping const. A const object cannot have its resource taken, so there is no sensible move from it. (There is a const T&& form; it exists, it binds to const rvalues, and it is almost never useful because you cannot steal from a const object. It is named here only so the combination does not look like an omission.)
5. Overloading on It
The pair that this whole mechanism exists to support:
void f(const std::string& s); // lvalue: somebody owns it -> copy
void f(std::string&& s); // rvalue: nobody does -> stealChapter 13.6 §4 measured the selection across seven arguments, and the pattern is: anything with a name goes to the first, anything in flight goes to the second.
Three things about this pair:
The const T& version is the fallback. If you provide only T&&, lvalues are refused entirely. If you provide only const T&, everything works and nothing is ever stolen. Providing both gives the cheap path where it is safe and the correct path everywhere else.
Ambiguity is not a risk here. An rvalue binds to both forms in principle, and T&& is a better match, so it wins. There is no tie.
And this is exactly the shape of the copy/move pair in Chapter 13.8: a copy constructor takes const T&, a move constructor takes T&&, and the compiler chooses by the category of the initializer.
6. What Happens With Only One of the Pair
Section 5 recommended providing both overloads. The two one-sided designs are worth measuring, because they fail in opposite directions and only one of them fails loudly.
Only const T& — measured with all three argument kinds:
g(s); const&
g(S{}); const&
g(std::move(s)); const&Everything compiles and nothing is ever stolen. The const T& overload binds to all three (§3's table), so an rvalue argument is accepted and then copied. This is the pre-C++11 situation: correct, with the cheap path simply unused. Chapter 13.8 §7 measures the cost as 7 copies and 11 allocations where 0 and 4 were available.
Only T&& — measured:
g(s);
error: no matching function for call to 'g'
note: candidate function not viable: expects an rvalue for 1st argument
g(S{}); &&
g(std::move(s)); &&Named objects cannot be passed at all, and the note is unusually direct — expects an rvalue for 1st argument.
So the two failures are:
| Design | Consequence |
|---|---|
const T& only | compiles everywhere, never moves — silently slow |
T&& only | compile error for named objects — loudly restricted |
Neither is wrong exactly. A const T&-only interface is what every pre-C++11 function was, and it remains correct. A T&&-only interface is occasionally deliberate — a function that insists on taking ownership, so a caller cannot accidentally hand it something they still intend to use, which is Chapter 14.8's subject.
What is almost always wanted is both, and the asymmetry is a useful reminder: the loud restriction is the safer mistake.
7. The Hazard
Chapter 13.6 §6 measured this and it belongs here in its practical form:
void takesRvalueRef(std::string&& r)
{
f(r); // ?
f(std::move(r)); // ?
} inside takesRvalueRef, passing r on: lvalue
...and with std::move(r): RVALUEr is an lvalue. It is a named variable; naming something makes the expression an lvalue, regardless of the variable's type. So f(r) selects the const T& overload and copies — inside a function whose whole purpose was to avoid copying.
This is the single commonest way a move silently becomes a copy, and it has a specific shape worth recognising: a function takes T&& so that it can steal, and then hands the parameter to something else without std::move.
The fix:
void takesRvalueRef(std::string&& r)
{
f(std::move(r)); // pass the category on, not just the object
}And the reason it is needed rather than automatic: the function might use r again afterwards. The language cannot know that the hand-off is the last use, so it requires you to say so. Chapter 13.9 is what std::move does and what you may do with r after.
8. What a T&& Parameter Means to the Caller
Worth stating explicitly, because it is a contract and not only a mechanism.
A function declaring void consume(Buffer&& b) is telling callers: I may take what this object owns, so give me something you are finished with. The caller must either hand over a temporary or write std::move to signal consent. Chapter 13.9 §1's framing is that std::move is how a caller grants permission.
That makes T&& a useful interface signal even before the move operations exist:
void f(const T&) — I will read it, you keep it (Chapter 6.5).
void f(T&) — I will modify it in place, you keep it (Chapter 6.4).
void f(T&&) — I may gut it, you are finished with it.
void f(T) — I get my own copy (Chapter 6.2), which Chapter 13.2 §4 measured as a hidden copy at every call.
Chapter 6.9 §5 argued that parameter forms should state intent; this is a fourth form with a fourth intent, and it is Chapter 14.8's subject when applied to ownership deliberately.
9. One Thing That Looks the Same and Is Not
This matters enough to name explicitly, because reading T&& as "rvalue reference" everywhere will eventually produce a wrong conclusion.
void f(std::string&& s); // rvalue reference — this chapter
template <typename T>
void g(T&& x); // NOT an rvalue referenceIn the second, T is a deduced template parameter, and T&& there is a different construct with different rules — it binds to lvalues and rvalues and preserves which one it got. It is usually called a forwarding reference, it exists to write generic code that passes arguments on without changing their category, and it needs std::forward rather than std::move.
All of that belongs to Module 18. The distinction to carry from here: T&& on a concrete type is an rvalue reference; T&& on a deduced template parameter is not. The syntax collision is unfortunate and permanent.
10. What to Actually Do
Read T&& as "a reference to something nobody will use again."
Use it as the parameter form for a function that may take the argument's resource.
Provide const T& alongside it so lvalues still work.
Expect T&& to refuse a named object — that is the point, not an inconvenience.
Write std::move when passing a T&& parameter onward, or it copies (§6).
Use the four parameter forms as intent signals — read / modify / consume / copy.
Do not confuse it with T&& on a deduced template parameter — Module 18.
And ignore const T&& unless you have a specific reason; you cannot steal from a const object.
11. Mistakes Worth Making Once
Passing a T&& parameter onward by name. Measured: selects the lvalue overload and copies.
Expecting T&& r = s; to work. Refused — rvalue reference … cannot bind to lvalue.
Expecting T&& r = cs; to work. Refused — drops const.
Providing only a T&& overload. Named objects cannot be passed at all.
Providing only const T&. Correct forever, fast never.
Thinking T&& means "this is an rvalue". It is a type; the named parameter is an lvalue.
Reading T&& in a template as an rvalue reference. Different construct, Module 18.
Using const T&&. Binds const rvalues and cannot steal from them.
12. Interview Relevance
"What does T&& mean?" An rvalue reference — a reference that binds only to rvalues, i.e. to objects nobody will use again. Adding why that restriction is the feature is the better answer.
"What binds to what?" The three-row table. The detail that distinguishes: const T& binds to everything, which is why it is the universal parameter form and why it cannot be the move parameter.
"Inside f(T&& x), can you pass x to another function and expect a move?" No — x is an lvalue, so you need std::move(x). This is the question that separates understanding from recognition.
"Why does T&& r = s; not compile?" Because s is an lvalue that somebody still owns; binding an rvalue reference to it would claim otherwise.
"Is template <typename T> void f(T&& x) an rvalue reference?" No — a forwarding reference, with different binding rules and std::forward rather than std::move. Knowing the distinction exists is a strong signal.
13. What You Should Be Able to Say Now
T&&is an rvalue reference — a reference binding only to expressions nobody owns.const T&cannot serve the purpose — it binds to everything and forbids modification.T&cannot either — it refuses rvalues.- Measured table:
T&binds onlys;const T&binds all three;T&&binds only the rvalue. - Measured messages:
rvalue reference … cannot bind to lvalue, anddrops 'const' qualifierforcs. T&& r = std::move(s);binds — becausestd::move(s)is an rvalue expression.- The restriction is the feature: the stealing overload is unreachable from an lvalue.
- The pair
const T&+T&&gives the cheap path where safe and the correct path everywhere. - An rvalue prefers
T&&— no ambiguity. - Measured hazard: a named
T&¶meter is an lvalue, so passing it on copies. - The guarantee stops at the signature — inside the body,
std::moveis your responsibility. - Four parameter forms, four intents:
const T&read,T&modify,T&&consume,Tcopy. T&&on a deduced template parameter is a different construct (Module 18).const T&&exists and is almost never useful.
14. Exercises
Exercise 1 — the table. Reproduce the three-by-three binding table from memory and give the error message for one refused cell.
Exercise 2 — predict. Which compile: std::string&& a = s;, std::string&& b = makeS();, std::string&& c = std::move(s);, std::string&& d = cs;, const std::string& e = makeS();
Exercise 3 — find the copy. void sink(Buffer&& b) { store(b); } where store has both overloads. Say which is chosen, why, and the fix.
Exercise 4 — why refuse? Explain why T&& r = s; is refused even though s is modifiable and you own it.
Exercise 5 — one or both? Say what breaks if a function provides only T&&, and what is lost if it provides only const T&.
Exercise 6 — the intent signals. For each, choose a parameter form and justify it: a function that prints a buffer; one that fills a buffer in place; one that takes ownership of a buffer; one that needs its own independent copy.
Exercise 7 — not the same. Explain the difference between void f(std::string&&) and template <typename T> void f(T&&), and which helper each needs.
15. Summary
T&&binds only to rvalues — a reference to something nobody will use again.const T&binds to everything (so cannot distinguish) andT&refuses rvalues (so cannot accept temporaries).- Measured, nine cases:
T&&refused a non-const lvalue and a const lvalue, and boundmakeS()andstd::move(s). - The restriction is the feature — the stealing overload is unreachable from an lvalue, enforced at every call.
- Provide
const T&andT&&as a pair; an rvalue prefersT&&with no ambiguity. - Measured hazard: a named
T&¶meter is an lvalue, so passing it on copies — usestd::move. - The guarantee stops at the signature.
- Four parameter forms, four intents.
T&&on a deduced template parameter is something else (Module 18).
The parameter form exists and nothing uses it yet. Two functions need it, and they are the two the Rule of Three was missing: a constructor that builds a new object by taking an expiring one's resource rather than duplicating it, and an assignment that does the same to an object that already exists. Both are written with the form from this chapter, both are short, and both have one obligation that the copy versions do not — the object they stole from is still alive, its destructor will still run, and it must be left in a state where that destructor does something harmless. The next chapter writes both, with the measurement that justifies the whole second half of this module: the container growth that cost seven copies and eleven allocations, performed again at zero copies and four.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
