References

Lvalue References

Lippman:

  • Ordinarily, when we initialize a variable, the value of the initializer is copied into the object we are creating
  • When we define a reference, instead of copying the initializer’s value, we bind the reference to its initializer
  • Once initialized, a reference remains bound to its initial object
  • There is no way to rebind a reference to refer to a different object
  • Because there is no way to rebind a reference, references must be initialized

Liberty:

  • idea
    • Normally, when you use a reference, you do not use the address-of operator. You simply use the reference as you would use the target variable.
      • references are aliases for their target
    • passing by reference enables the function to change the object being referred to
  • references (unlike other variables)
    • must be initialized when they are declared
    • cannot be reassigned
  • space before the address-of operator is required
    • wrong: see counterexample from cppreference:
// https://en.cppreference.com/w/cpp/language/reference_initialization
// - shows that the space before the "&" is NOT required
double d = 2.0;
double& rd = d;        // rd refers to d
const double& rcd = d; // rcd refers to d

Address of a Reference

Liberty:

  • If you ask a reference for its address, it returns the address of its target.
// address of a reference
int intOne;
int &rSomeRef = intOne;
intOne = 5;

// intOne: 5
// rSomeRef: 5
// &intOne: 0x3500
// &rSomeRef: 0x3500
  • from isocpp:
    • Unlike a pointer, once a reference is bound to an object, it can not be “reseated” to another object.
    • The reference itself isn’t an object
      • it has no identity
      • taking the address of a reference gives you the address of the referent
      • remember: the reference is its referent

Initialization

Lippman:

  • usually “the type of a reference must match the type of the object to which it refers
  • a reference may be bound only to an object, not to a literal or to the result of a more general expression:
int &refVal4 = 10;    // error: initializer must be an object
double dval = 3.14;
int &refVal5 = dval;  // error: initializer must be an int object

Reference to Reference

What does this mean?: “Because references are not objects, we may not define a reference to a reference.”, (Lippman, p51)

From stackoverflow:

// https://stackoverflow.com/q/28359555/12282296
int main(){
  int ival=1024;
  int &refVal=ival;
  int &refVal2=refVal;
  return 0;
}

After refVal is initialized, whenever you mention its name, it behaves like the variable ival it refers to—its “referenceness” can no longer be detected (except by decltype). Therefore refVal2 is simply initialized to refer to ival also.

There is no type “reference to reference to int”, int&(&).

reference to const

  • aka “const reference”, “lvalue reference to a const value”
  • X const& x is equivalent to const X& x, and X const* x is equivalent to const X* x. (see isocpp.org)
  • Unlike an ordinary reference, a reference to const cannot be used to change the object to which the reference is bound
  • “we can initialize a reference to const from any expression that can be converted to the type of the reference”
    • we can bind a reference to const to
      • a nonconst object,
      • a literal, or
      • a more general expression
      • an object of a different type (in this case, the reference is bound to a temporary object, see p.62)
  • “Binding a reference to const to an object says nothing about whether the underlying object itself is const.”
    • ie. the underlying object cannot be changed by using the reference to const, but it might be changed by other means
  • binding a non-const reference to a temporary is illegal in C++, only const references may be bound to a temporary (see p.62)
const int ci = 1024;
const int &r1 = ci;     // ok: both reference and underlying object are const
r1 = 42;                // error: r1 is a reference to const
int &r2 = ci;           // error: nonconst reference to a const object

// Initialization and References to const:
int i = 42;
const int &r1 = i;        // we can bind a const int& to a plain int object
const int &r2 = 42;       // ok: r1 is a reference to const
const int &r3 = r1 * 2;   // ok: r3 is a reference to const
int &r4 = r * 2;          // error: r4 is a plain, non const reference

// Temporaries and References to const:
// see examples in section "Temporaries"

// A Reference to const May Refer to an Object That Is Not const:
int i = 42;
int &r1 = i;          // r1 bound to i
const int &r2 = i;    // r2 also bound to i; but cannot be used to change i
r1 = 0;               // r1 is not const; i is now 0
r2 = 0;               // error: r2 is a reference to const

const references

From isocpp.org:

  • references are always const
  • X& const x is functionally equivalent to X& x. Since you’re gaining nothing by adding the const after the &, you shouldn’t add it: it will confuse people - the const will make some people think that the X is const, as if you had said const X& x.

Lip15.2.3:

  • we can bind a pointer or reference to a base-class type to an object of a type derived from that base class
  • examples:
    • use a Quote& to refer to a Bulk_quote object
    • assign the address of a Bulk_quote object to a Quote*
  • consequently, when we use a reference (or pointer) to a base-class type, we don’t know the actual type of the object to which the pointer or reference is bound
    • That object can be an object of the base class or it can be an object of a derived class
  • the smart pointer classes support this “derived-to-base conversion”, too

Rvalue References

  • “are primarily intended for use inside classes” (Lip2.3.1)
  • introduced by the new standard to support move operations
  • must be bound to an rvalue
  • obtained by using &&
  • may be bound only to an object that is about to be destroyed
    • so that we are free to “move” resources from an rvalue reference to another object
  • rvalue and lvalue references have the opposite binding properties:
int i = 42;
int &r = i;             // ok: r refers to i
int &&rr = i;           // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 42;       // error: i * 42 is an rvalue
const int &r3 = i * 42; // ok: we can bind a reference to const to an rvalue
int &&rr2 = i * 42;     // ok: bind rr2 to the result of the multiplication

Examples of expressions that return

  • lvalues
    • Functions that return lvalue references
    • operators
      • assignment
      • subscript
      • dereference
      • prefix increment/decrement (++i)
    • Variable expressions
  • rvalues (prvalues)
    • Functions that return a nonreference type
    • operators
      • arithmetic
      • relational
      • bitwise
      • postfix increment/decrement (i++)
    • literals
  • rvalue references refer to objects that are about to be destroyed
  • ephemeral vs persistent:
    • lvalues have persistent state, whereas rvalues are either literals or temporary objects
  • code that uses an rvalue reference is free to take over value/resources/state from the object to which the reference refers
    • “steal” value/resources/state from the object (rhs) bound to an rvalue reference (lhs)
  • you cannot directly bind an rvalue reference to a variable, even if that variable was defined as an rvalue reference type
int &&rr1 = 42;         // ok: literals are rvalues
// Error:
int &&rr2 = rr1;        // error: the expression rr1 is an lvalue!

Reference to Pointer

From stackoverflow:

  • a reference to a pointer is like a reference to any other variable
void fun(int*& ref_to_ptr)
{
    ref_to_ptr = 0; // set the "passed" pointer to 0
    // if the pointer is NOT passed by ref,
    // then only the copy(parameter) you received is set to 0,
    // but the original pointer(outside the function) is not affected.
}

This is often useful when returning a pointer type data member:

// whereas, if we would return the data member `_p` "by value", we 
// cannot modify it (eg. to make `_p` point to another address)
class X {
  double* _p{nullptr};
public:
  double*& p() {    // return by reference
    return _p;
  }
}

Pointer to Reference

From stackoverflow:

  • A pointer to reference is illegal in C++
    • because - unlike a pointer - a reference is just a concept that allows the programmer to make aliases of something else.
  • A pointer is a place in memory that has the address of something else, but a reference is NOT.

Forwarding Reference

  • The term universal reference was coined by Scott Meyers as a common term that could result in either an lvalue reference or an rvalue reference.
  • The C++17 standard introduced the term forwarding reference, because the major reason to use such a reference is to forward objects.
    • However, note that it does not automatically forward.
    • The term does not describe what it is but what it is typically used for.

Forwarding Reference vs Rvalue Reference

X&& for a specific type X

  • declares a parameter to be an rvalue reference
  • it can only be bound to a movable object (a prvalue, such as a temporary object, and an xvalue, such as an object passed with std::move())
    • see move constructor and move-assignment operator
  • it is always mutable
  • you can always “steal” its value

T&& for a template parameter T

  • declares a forwarding reference (also called universal reference)
  • it can be bound to a mutable, immutable (i.e., const), or movable object
  • it is mutable or immutable
    • const is never dropped!
  • you can sometimes “steal” its value

remove_reference_t

Problem: References must be initialized. Therefore, the following function template itself won’t work properly with lvalue arguments.

template<typename T> void f(T&& p) // p is a forwarding reference
{
  T x; // for passed lvalues, x is a reference
  ...
}

Solution: To deal with this situation, the std::remove_reference type trait is frequently used to ensure that x is not a reference

template<typename T> void f(T&& p) // p is a forwarding reference
{
  std::remove_reference_t<T> x; // x is never a reference
  ...
}

Perfect Forwarding

  • to write generic code that “forwards” the basic property of passed arguments
    • modifiable objects forwarded as modifiable
    • preserve constness
    • movable objects forwarded as movable
  • called perfect forwarding, because the result of calling g() indirectly through f() (or forwardToG() respectively) will be the same as if the code called g() directly
  • no additional copies are made

Example with Naive Forwarding

Forward a call of f() to a corresponding function g():

Without templates, we would have to program all three cases for f() separately:

#include <utility>
#include <iostream>

class X {
  ...
};

void g(X&) {
  std::cout << "g() for variable\n";
}
void g(X const&) {
  std::cout << "g() for constant\n";
}
void g(X&&) {
  std::cout << "g() for movable object\n";
}

// let f() forward argument val to g():
void f(X& val) {
  g(val);                 // val is non-const lvalue => calls g(X&)
}
void f(X const& val) {
  g(val);                 // val is const lvalue => calls g(X const&)
}
void f(X&& val) {
  g(std::move(val));      // val is non-const lvalue => needs std::move() to call g(X&&)
                          // without move() g(X&) would be called
}

int main()
{
  X v;              // create variable
  X const c;        // create constant

  f(v);             // f() for nonconstant object calls f(X&) => calls g(X&)
  f(c);             // f() for constant object calls f(X const&) => calls g(X const&)
  f(X());           // f() for temporary calls f(X&&) => calls g(X&&)
  f(std::move(v));  // f() for movable variable calls f(X&&) => calls g(X&&)
}

Example with Perfect Forwarding

The same using perfect forwarding to forward arguments:

#include <utility>
#include <iostream>

class X {
  ...
};

void g(X&) {
  std::cout << "g() for variable\n";
}
void g(X const&) {
  std::cout << "g() for constant\n";
}
void g(X&&) {
  std::cout << "g() for movable object\n";
}

// let f() perfect forward argument val to g():
template<typename T>
void f(T&& val) {
  g(std::forward<T>(val)); // call the right g() for any passed argument val
}

int main()
{
  X v;                  // create variable
  X const c;            // create constant

  f(v);                 // f() for variable calls f(X&) => calls g(X&)
  f(c);                 // f() for constant calls f(X const&) => calls g(X const&)
  f(X());               // f() for temporary calls f(X&&) => calls g(X&&)
  f(std::move(v));      // f() for move-enabled variable calls f(X&&) => calls g(X&&)
}

Forwarding Arguments using static_cast<T&&>

Forward the argument along to another function g():

  • problem: the expression x will always be an lvalue (recall: variables are lvalues)
  • solution: the static_cast casts x to its original type and lvalue- or rvalue-ness, thereby achieving perfect forwarding
class C {
  ...
};

void g(C&);
void g(C const&);
void g(C&&);

template<typename T>
void forwardToG(T&& x)
{
  g(static_cast<T&&>(x)); // forward x to g()
}

void foo()
{
  C v;
  C const c;
  forwardToG(v);              // eventually calls g(C&)
  forwardToG(c);              // eventually calls g(C const&)
  forwardToG(C());            // eventually calls g(C&&)
  forwardToG(std::move(v));   // eventually calls g(C&&)
}

Forwarding Arguments using std::forward<T>

best practice:

  • use std::forward<>() (in header <utility>) instead of static_cast for perfect forwarding
    • better documents the programmer’s intent
    • prevents errors (such as omitting one &)
#include <utility>

template<typename T> 
void forwardToG(T&& x)
{
  g(std::forward<T>(x)); // forward x to g()
}
// std::forward<T> is essentially a static_cast<T&&>
// (but it is more convenient than a static_cast<T&&>)
template <typename T>
T&& forward(std::remove_reference_t<T>& t) {
    return static_cast<T&&>(t);
}

Thus, preserving type information is a two-step process:

  1. To preserve type information in the arguments, we must define forwardToG’s function parameters as rvalue references to a template type parameter T&&.
  2. We must use forward to preserve the arguments’ original types when forwardToG passes those arguments to g.