Expert20 minC++ Fundamentals
UpdatedAug 4, 2026
Edit

Value Categories and Forwarding

CONCEPTS:C++ Value Categories and Forwarding

Question Variations

  • "What is the difference between an rvalue and an rvalue reference?"
  • "Why is a named `T&&` variable an lvalue?"
  • "What does `std::forward` do that `std::move` does not?"
  • "When should a function use a forwarding reference?"

Why This Is Asked

Value categories explain overload selection, move semantics, and generic wrapper design. Interviewers use this question to assess whether you can distinguish an rvalue reference from an rvalue and use std::forward correctly without moving from an argument unintentionally.

Key Concepts

  • Lvalues have persistent identity; rvalues are commonly temporary or expiring values.
  • T&& is an rvalue reference in an ordinary declaration.
  • In a deduced context, T&& can be a forwarding reference.
  • A named rvalue-reference variable is an lvalue expression.

Question Variations

  • “What is the difference between an rvalue and an rvalue reference?”
  • “Why is a named T&& variable an lvalue?”
  • “What does std::forward do that std::move does not?”
  • “When should a function use a forwarding reference?”

Answers by Technology

+ Add Variant
C++Improve this answer ✏️

Expected Answer (C++23)

An lvalue is an expression with identity that can generally be referred to again; an rvalue is an expression used to initialize or move from another object. T&& is an rvalue reference type, but a named variable of that type is itself an lvalue expression because it has a stable name.

In a template where T is deduced, T&& is a forwarding reference. std::forward<T> preserves the caller’s original value category when passing the argument onward. By contrast, std::move unconditionally casts to an xvalue.

#include <utility>

void consume(int& value) { value += 1; }
void consume(int&& value) { value += 10; }

template <typename T>
void relay(T&& value) {
    consume(std::forward<T>(value));
}

int main() {
    int number = 1;
    relay(number); // Calls consume(int&).
    relay(1);      // Calls consume(int&&).
    return number == 2 ? 0 : 1;
}

Why It Matters

Perfect forwarding lets generic factories and wrappers preserve caller intent without needless copies. Misusing it can select the wrong overload, move from reusable data, or make template errors difficult to diagnose.

Common Mistakes

  • Calling std::move on every forwarding-reference parameter: This treats lvalue callers as rvalues and may move from values they expected to retain.
  • Expecting a named T&& parameter to call an rvalue overload: It is an lvalue until explicitly forwarded or moved.
  • Using forwarding references in simple APIs unnecessarily: They complicate overload resolution; explicit value or reference parameters are often clearer.

Follow-up Questions

  • What does std::forward<T> preserve? (Answer: Whether the original argument was an lvalue or rvalue.)
  • When is std::move appropriate? (Answer: When the current code intentionally treats an object as expiring and will not rely on its prior value.)