COMS W4995 C++ Deep Dive for C Programmers

Forwarding References & Variadic Templates

Lecture outline

Value categories (values.cpp)

  1. L-values and r-values revisited

    • struct D
    • Binding lvalue
    • Binding rvalue
      • unnamed object bound to an r-value ref is mutable
      • temporary object’s lifetime is extended to that of reference
  2. R-value reference is an l-value

  3. Summary of value categories

    • L-values:
      • named variables, function call returning by reference, etc.
      • ex) i, s[i] (assuming i is int, and s is std::string)
    • R-values have two sub-categories: prvalues and xvalues

    • prvalues (for pure r-values):
      • literals, function calls returning by value, unnamed temp objects
      • ex) 2.0, s + s (where s is a string), string{"abc"}
    • xvalues (for expiring values):
      • function calls returning rvalue ref or cast expressions to rvalue ref
      • ex) std::move(s)
    • Some surprises:
      • C string literals (ex. “abc”) are lvalues because they have addresses
      • rvalue reference itself is an lvalue because it has a name!
    • Another way to group value categories:
      • glvalue (for generalized lvalue): lvalue + xvalue
      • prvalues: r-values that are not xvalues

Forwarding Reference (fwd-ref.cpp)

  1. template <typename T> void f1(T t) { t += 0.1; }

    • we can pass lvalue or rvalue but t is a copy
  2. template <typename T> void f2(T& t) { t += 0.2; }

    • cannot pass rvalue
  3. template <typename T> void f3(T&& t) { t += 0.3; }

    • T&& t parameter is called a forwarding reference if T is a type parameter of a function template

      D d1 {1.0};
      f3(d1);     // lvalue arg: T = D&, so T&& is D&
      f3(D(2.0)); // rvalue arg: T = D,  so T&& is D&&
      
    • C++ collapses references as follows in templates and type aliases:

      X&  &  collapses to X&
      X&  && collapses to X&
      X&& &  collapses to X&
      X&& && collapses to X&&
      
  4. template <typename T> void f4(T&& t) { D d{t}; d += 0.4; }

    • Forwarding references allow you to accept an l-value as an l-value reference and an r-value as an r-value reference
    • But a forwarding reference itself, whether it is an l-value reference or r-value reference, is always an l-value.
    • Thus D d{t}; is always a copy construction.
  5. template <typename T> void f5(T&& t) { D d{std::forward<T>(t)}; d += 0.5; }

    • std::forward<T>() allows us to forward a forwarding reference without losing its original value category; this is called perfect forwarding
  6. template <typename T> void f6(T&& t) { D d{c2cpp::forward<T>(t)}; d += 0.6; }

    • A simplifed implementation of std::forward<T>():

      template<typename T>
      T&& forward(remove_reference_t<T>& t) noexcept {
          return static_cast<T&&>(t);
      }
      

Variadic Templates (variadic.cpp)

  1. Basic variadic template using template recursion

  2. Function parameters as forwarding references to reduce copying

  3. Fold expression