Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void f(std::string&& s);          // an rvalue reference parameter

Two 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 / initializers (lvalue)makeS() (rvalue)cs (const lvalue)
T&bindsrefusedrefused
const T&bindsbindsbinds
T&&refusedbindsrefused

The exact messages, because they are informative:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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' qualifier

And 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void f(const std::string& s);     // lvalue: somebody owns it -> copy
    void f(std::string&& s);          // rvalue: nobody does      -> steal

Chapter 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

DesignConsequence
const T& onlycompiles everywhere, never moves — silently slow
T&& onlycompile 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void takesRvalueRef(std::string&& r)
    {
        f(r);                  // ?
        f(std::move(r));       // ?
    }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  inside takesRvalueRef, passing r on:    lvalue
  ...and with std::move(r):                RVALUE

r 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void f(std::string&& s);          // rvalue reference — this chapter

    template <typename T>
    void g(T&& x);                    // NOT an rvalue reference

In 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

  1. T&& is an rvalue reference — a reference binding only to expressions nobody owns.
  2. const T& cannot serve the purpose — it binds to everything and forbids modification.
  3. T& cannot either — it refuses rvalues.
  4. Measured table: T& binds only s; const T& binds all three; T&& binds only the rvalue.
  5. Measured messages: rvalue reference … cannot bind to lvalue, and drops 'const' qualifier for cs.
  6. T&& r = std::move(s); binds — because std::move(s) is an rvalue expression.
  7. The restriction is the feature: the stealing overload is unreachable from an lvalue.
  8. The pair const T& + T&& gives the cheap path where safe and the correct path everywhere.
  9. An rvalue prefers T&& — no ambiguity.
  10. Measured hazard: a named T&& parameter is an lvalue, so passing it on copies.
  11. The guarantee stops at the signature — inside the body, std::move is your responsibility.
  12. Four parameter forms, four intents: const T& read, T& modify, T&& consume, T copy.
  13. T&& on a deduced template parameter is a different construct (Module 18).
  14. 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) and T& refuses rvalues (so cannot accept temporaries).
  • Measured, nine cases: T&& refused a non-const lvalue and a const lvalue, and bound makeS() and std::move(s).
  • The restriction is the feature — the stealing overload is unreachable from an lvalue, enforced at every call.
  • Provide const T& and T&& as a pair; an rvalue prefers T&& with no ambiguity.
  • Measured hazard: a named T&& parameter is an lvalue, so passing it on copies — use std::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.