Work and Note

Cpp Ref

Follow me on GitHub

value category

  • expression
    • glvalue
      • lvalue
      • xvalue
    • rvalue
      • prvalue
      • xvalue
  1. A glvalue [(generalized lvalue)] is an expression whose evaluation determines the identity of an object, bit-field, or function.
  2. A prvalue is an expression whose evaluation initializes an object or a bit-field, or computes the value of an operand of an operator, as specified by the context in which it appears.
  3. An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime).
  4. An lvalue is a glvalue that is not an xvalue.
  5. An rvalue is a prvalue or an xvalue.

why use rvalue

Simply put, an rvalue is object without name. And rvalue enables the move semantics.

basic note

  1. You can overload a function to take an lvalue reference and an rvalue reference.
  2. The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue reference as an rvalue.
    1. In order to keep original type, you will need perfect forwardiing
  3. You can cast an lvalue to an rvalue reference. (via std::move)
  4. Function templates deduce their template argument types and then use reference collapsing rules.
    1. T&& is given the name universal reference. And it does not always resolve to a rvalue reference.
#include <iostream>
#include <utility>

void func(int const&)
{
    std::cout << "lvalue" << std::endl;
}

void func(int&&)
{
    std::cout << "rvalue" << std::endl;
}

template<typename T>
void wrap(T&& a) {
    func(std::forward<T>(a));
    func(a);
}

int main() {
    int a = 0;
    int && b = std::move(a);
    // print lvalue, lvalue
    wrap(a);
    wrap(b);
    // print rvalue, lvalue
    wrap(0);
    wrap(std::move(b));
}

rvalue reference

  1. std::move is a simple cast. It does not put lvalue in undefined state if the result is not consumed immediately.
  2. Thus, use std::move to generate rvalue value reference adds no value
  3. However, rvalue reference can be used to extend the lifetime of returned rvalue.

reference collapsing

& & -> &
& && -> &
&& & -> &
&& && -> &&

perfect forwarding

  • maintain orignal type when passing to another function.
    • if we depends on reference collapsing only, the type could change implicitly. (note 2)
  • avoid writting multiple function for different value category

copy elision

  1. rvalue can be bound to const lvalue reference
    1. as a result, rvalue can be used to call copy constructor
    2. in fact, for strong exception guranatee, nonexcept move > copy > regular move
  2. the standard allow copy to elided for some cases:
    1. RVO / NRVO: return temporary object / named object from a function
    2. etc
  3. some caveat:
    1. the code still had to be able to work as if the copy were not elided. Namely, there had to be an accessible copy and/or move constructor.
    2. copy elision can be applied even if copying/moving the object has side-effects
  4. C++ 17 gurateed copy elision
    1. Changes the defition of prvalues. No temporay object is created. i.e. no copy is needed
std::string a() {
    return "a pony";
}

std::string b() {
    return a();
}

int main() {
    auto x = b();
}

// In the C++11 model, return "a pony"; initializes the temporary return object of a(),
// which move-constructs the temporary return object of b(), which move-constructs x.
// All the moves are likely elided by the compiler.

// In the C++17 model, return "a pony"; initializes the result object of a(), which is the result object of b(), which is x.

pass by value or reference

For constructor,

  1. If we pass by reference, we cannot be sure if the reference is stored in the class.
  2. If we pass by value, we do not need to care about it storage.
    1. We can also save the performance by doing two moves. One move on the caller, one move on the callee.
    2. Otherwise, it will still work with two copies, or one copy + one move.
    3. We also avoid multiple overload of the function.
  3. Thus, pass by value is preferred

For functions, we might have a problem with unnecessary dealloc.

  1. Ex, string has a internal capacity that get re-used on copy
  2. If we pass by value and move it, the string will always dealloc.
    1. No dealloc on constructor because there is no variable to dealloc.
  3. If we pass by const reference, we have the choice of move or copy
  4. However, most classes do not have this re-use pattern. Thus, both patterns can be helpful.

ref-qualified member functions (C++11)

The implicit object parameter this will have a corresponding type reference.

template <typename T>
class optional
{
  // ...
  T&       value() &;
  T&&      value() &&;
  T const& value() const&;
};

reference

Rvalue references in Chromium Reference collapsing forwarding