ProgrammingC++ Developer

What are 'lvalue' and 'rvalue' in C++? Explain their differences, ways to pass them to functions, and why rvalue references were introduced?

Pass interviews with Hintsage AI assistant

Answer

In C++, lvalue (left value) is an expression that refers to an object in memory which has a name and can be referenced (e.g., a variable). rvalue (right value) is a temporary value that does not have a name and is not an object in the traditional sense (e.g., the result of a + b, literals like 5).

Lvalue can have its address taken, while rvalue cannot (before rvalue references were introduced). For passing them to functions, there are:

  • lvalue references: void foo(const std::string& s); — accept both lvalue and rvalue.
  • Lvalue references: void bar(std::string& s); — accept only lvalue.
  • Rvalue references (C++11+): void baz(std::string&& s); — accept only rvalue.

Example:

void takeValue(std::string& s) { } // lvalue void takeRValue(std::string&& s) { } // rvalue std::string s = "hello"; takeValue(s); // OK, lvalue takeRValue(std::string("hi")); // OK, rvalue

Rvalue references are needed for efficient transfer of temporary objects, primarily for move semantics, to move resources instead of copying them.

Tricky Question

What type of reference (lvalue or rvalue) does the expression std::move(obj) yield? What category does the object itself become after applying std::move?

Answer:

std::move(obj) always returns an rvalue reference (T&&), but the object itself remains an lvalue; it just undergoes an explicit conversion. After this, one must handle the object very carefully (it may be in an undefined, but valid state).

Example:

std::string s = "data"; std::string d = std::move(s); // d takes the data from s, s is now empty

Examples of real errors due to misunderstanding the subtleties of the topic.


History

In a large project, one of the developers passed temporary objects through an lvalue reference T& (instead of T&& or const T&). This led to compilation errors and suboptimal copies — move semantics were not used, and performance dropped by 40%.


History

In the frontend engine, std::move was incorrectly applied to variables that were used again afterward. As a result, the variables were in a "destroyed" state, leading to crashes and rendering thread failures.


History

In the serialization library, a container of type std::vector<T> was passed to a function as an lvalue, but move was expected. Instead of moving, expensive copying of a large number of elements occurred, which significantly worsened serialization time on large arrays.