Fundamentals 47 min read

C++ Value Categories: lvalue, prvalue, xvalue, and Their Role in Function Returns and Move Semantics

The article explains how C++ value categories—lvalue, prvalue, and xvalue—govern function return handling, parameter passing, and object lifetimes, detailing hidden out‑parameters, copy‑elision, const‑reference lifetime extension, rvalue references, and the role of std::move in enabling move semantics.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
C++ Value Categories: lvalue, prvalue, xvalue, and Their Role in Function Returns and Move Semantics

This article explores the evolution of C++ value categories—lvalue, prvalue, and xvalue—by examining how the language and compiler handle function return values, parameter passing, and object lifetimes.

1. From C to C++: Stack Layout and Return Values

In C, function parameters and local variables are stored on the stack. The article shows a simple C function and its AMD64 assembly, illustrating that arguments and return values are passed via registers or stack slots.

void Demo() {
int a = 0;
long b = 1;
short c = 2;
}

Corresponding assembly (simplified):

push   rbp
mov    rbp, rsp
mov    DWORD PTR [rbp-4], 0
mov    QWORD PTR [rbp-16], 1
mov    WORD PTR [rbp-18], 2
pop    rbp
ret

When a function returns a scalar, the value is placed in a register (e.g., eax). int Demo() { return 5; } Assembly:

push   rbp
mov    rbp, rsp
mov    eax, 5
pop    rbp
ret

2. Returning Complex Types

For structs, the compiler may allocate a hidden “out‑parameter” on the caller’s stack and pass its address to the callee. The callee constructs the object in that memory.

struct Test { long a, b; };
Test Demo() {
Test t = {1, 2};
return t;
}

Assembly (simplified):

push   rbp
mov    rbp, rsp
mov    QWORD PTR [rbp-16], rdi   ; store hidden out‑param address
...                         ; construct local t
mov    rax, QWORD PTR [rbp-16]   ; return pointer
pop    rbp
ret

If the caller receives the result in a variable, the compiler may copy from the temporary (xvalue) into the variable, then destroy the temporary. Test t = Demo(); Resulting assembly shows an extra copy and a destructor call for the temporary.

3. Summary of Return‑Value Handling

When the result fits in a register, it is returned directly (prvalue).

When the result needs memory, the caller provides a hidden slot; the callee writes into it (treated as an lvalue).

If only a part of the result is used, the compiler creates an anonymous temporary (xvalue) that is destroyed after the expression.

4. References and Addressability

Const references can bind to any rvalue (including temporaries). The reference itself is an lvalue, but it extends the lifetime of the temporary it binds to.

const Test& ref = Demo();   // extends lifetime of the temporary

Non‑const lvalue references cannot bind to temporaries because temporaries have no addressable storage. Test& r = Demo(); // illegal Thus, a const reference to a return value behaves like a normal variable that owns its storage.

5. Rvalue References and Move Semantics

Rvalue references ( T&&) were introduced to enable move semantics. When a function overload takes T&&, a direct function return value prefers that overload.

void f(const Test&);   // for lvalues
void f(Test&&);        // for temporaries
f(Demo());            // calls the rvalue‑reference overload

Inside a move constructor, the source object’s resources are transferred (shallow copy) and the source is left in a valid but unspecified state (often nullified).

Test(Test&& other) : buf_(other.buf_) {
other.buf_ = nullptr;
}

Using a variable to receive a return value now benefits from copy‑elision (C++17), so no extra temporary is created. Test t = Demo(); // copy‑elision, no extra xvalue 6. std::move std::move is a utility that casts an lvalue to an rvalue reference, allowing the programmer to explicitly request move semantics.

String s1;
String s2 = std::move(s1);   // forces move construction

Note that std::move does not change the object’s actual value category; it merely enables overload resolution to select the move constructor.

7. Practical Takeaways

Value categories exist to reconcile C‑style low‑level calling conventions with high‑level language semantics.

prvalue = pure rvalue (no storage), xvalue = “expiring” value with temporary storage, lvalue = named object with storage.

Const references extend the lifetime of temporaries; rvalue references enable move semantics.

Copy‑elision (C++17) removes unnecessary temporaries for return‑value initialization. std::move is a cast, not a mover; the actual move occurs in the move constructor/assignment.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AssemblyC++lvaluemove semanticsprvaluervaluevalue categoriesxvalue
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.