• expression: sequence of operators and operands that specify a computation.
    • a fn call is an operator, apart from obvious operators.
  • statements in the “obvious” sense MAY contain expressions.

value categories

these are tagged to an expression and NOT to variables or types.

the compiler goes from the smallest of expressions to their compositions and uses value categories to “tag” an expression, which determines how it’ll treat its computed result.

three main categories based on (has identity), IsMovable(binds to T&&): note: having identity != addressable via &. you cannot use & on xvalues.

  • lvalue yes, no no accidental resource theft
  • prvalue no, yes transient, lifetime ends at statement
  • xvalue yes, yes evaluates to mem location but tagged EXPIRING, basically its memory is ABOUT to be gutted

The primary purpose: overload resolution

example

std::move(x) + 5
  • x is lvalue
  • 5 is prvalue (pure)
  • move casts it to && xvalue
  • the final result of sum is prvalue

now prvalue and lvalue are easy. when is it an xvalue?

  • moves are obv.
  • calling funcs on prvalues, compiler needs to materialize so then it’s an xvalue.
  • calling member/subscript of an rvalue.
  • function returning && is xvalue.

Why const T& binds to everything?

move semantics was new, people made const T& bind to temporaries and this did lifetime extension. so right now both T&& and const T& do lifetime extension.

failures: bind to ref of ref

const std::string& GetMin(const std::string& a, const std::string& b) {
    return (a < b) ? a : b;
}
 
// GetString() creates a temporary.
// we pass it to GetMin.
// the temporary naturally lives until the end of this full statement (the semicolon).
// BUT GetMin returns a reference. 
// we bind 'my_ref' to that returned reference, NOT directly to the prvalue.
const std::string& my_ref = GetMin(GetString(), "hello");
 
// CRASH! The temporary died the moment GetMin finished executing. 
// my_ref is dangling.
  • this lifetime extension DOES NOT apply to member initializer lists.

what about auto&&?

reference collapsing:

Deduced TypeThe && in the codeCollapsed ResultWhat it actually becomes
T&&&T&Lvalue reference
T&&&&T&&Rvalue reference
T&&T&Lvalue reference
T&&&T&Lvalue reference

same lifetime extension happens here too.

internals of a range based for loop:

{
    // The compiler silently injects auto&& to extend the lifetime!
    auto&& __range = GetNumbers(); 
    
    auto __begin = __range.begin();
    auto __end = __range.end();
    for ( ; __begin != __end; ++__begin) {
        int num = *__begin;
        std::cout << num;
    }
} // The extended lifetime of the temporary vector ends here.

the two broad names

  • glvalue = lvalue OR xvalue
  • rvalue = prvalue OR xvalue

Note that it’s an OR very much so.

If the compiler needs to look at the actual memory (polymorphism, virtual calls), it checks: “Is this a glvalue?”

If the compiler needs to destroy or steal the memory (move constructors), it checks: “Is this an rvalue?”

xvalue is like the BRIDGE from lvalue to rvalue.