Chapter 16: References

Compound Data Types

Functions Pointer types Pointer to member types Reference types Enumerated types Class types
Arrays Pointer to object Pointer to data member L-value references Unscoped enumerations Structs
Pointer to function Pointer to member function R-value references Scoped enumerations Classes
Unions

Examples

int return5()
{
    return 5;
}

int x{ 5 }; // 5 is an rvalue expression
const double d{ 1.2 }; // 1.2 is an rvalue expression

int y { x };          // x is a modifiable lvalue expression
const double e { d }; // d is a non-modifiable lvalue expression
int w { x + 1 };               // x + 1 is an rvalue expression
int q { static_cast<int>(d) }; // the result of static casting d to an int is an rvalue expression

Lvalue and Rvalue Expressions

If &(expression); compiles, expression must be an lvalue:

int x { 5 };
&x; // compiles: x is an lvalue expression
&5; // doesn't compile: 5 is an rvalue expression

References

A reference is essentially identical to the object being referenced. Once a reference has been defined, any operation on the reference is applied to the object being referenced. Reference variables follow the same scoping and duration rules that normal variables do.

Lvalue Reference Types

int x { 5 }; // x is a normal integer variable
int& ref { x }; // ref is an lvalue reference variable that can now be used as an alias for variable x

std::cout << x << '\n';   // print the value of x (5)
std::cout << ref << '\n'; // print the value of x via ref (5)

ref = 7; // the object being referenced (x) now has value 7
std::cout << x << ref << '\n'; // prints 7

int& invalidRef; // error: references must be initialized
int& invalidRef { y }; // invalid: can't bind to a non-modifiable lvalue
int& invalidRef2 { 0 }; // invalid: can't bind to an rvalue
double y { 6.0 };
int& invalidRef { y }; // invalid; reference to int cannot bind to double variable

int& ref { x }; // ref is now an alias for x
ref = y; // assigns 6 (the value of y) to x (the object being referenced by ref)
// The above line does NOT change ref into a reference to variable y!
// it changes x to var of y

Lifetime of a Reference

int x { 5 };
{
    int& ref { x }; // ref is a reference to x
    std::cout << ref << '\n'; // prints value of ref (5)
} // ref is destroyed here -- x is unaware of this
std::cout << x << '\n'; // prints value of x (5)

Dangling Reference

References Are Not Objects

int var{};
int& ref1{ var }; // an lvalue reference bound to var
int& ref2{ ref1 }; // an lvalue reference bound to var

Const References

const int x { 5 }; // x is a non-modifiable lvalue
const int& ref { x }; // okay: ref is a lvalue reference to a const value

Binding to Temporary Objects

const double& r1 { 5 }; // temporary double initialized with value 5, r1 binds to temporary
char c { 'a' };
const int& r2 { c }; // temporary int initialized with value 'a', r2 binds to temporary

Binding to Rvalues

const int& ref { 5 }; // The temporary object holding value 5 has its lifetime extended to match ref

Constexpr Reference

static int s_x { 6 };
[[maybe_unused]] constexpr int& ref2 { s_x }; // ok, can bind to static local

static const int s_x { 6 }; // a const int
[[maybe_unused]] constexpr const int& ref2 { s_x }; // needs both constexpr and const

Pass by Value

Passing by value involves copying the value, which can be cheap (e.g., int) or expensive (e.g., string).

void printValue(int y)
{
    std::cout << y << '\n';
} // y is destroyed here

int main()
{
    int x { 5 };
    printValue(x); // x is passed by value (copied) into parameter y
}

Pass by Reference

Passing by reference allows functions to modify the original object.

#include <iostream>

void addOne(int& y) // y is bound to the actual object x
{
    ++y; // this modifies the actual object x
}

int main()
{
    int x { 5 };
    addOne(x); // x has been modified
}

Const References

void printValue(int& y) // y only accepts modifiable lvalues
{
    std::cout << y << '\n';
}

void printRef(const int& y) // y is a const reference
{
    std::cout << y << '\n';
    // ++y; // not allowed: y is const
}

int main()
{
    int x { 5 };
    printValue(x); // ok: x is a modifiable lvalue
    printRef(x); // ok

    const int z { 5 };
    // printValue(z); // error: z is a non-modifiable lvalue
    printRef(z); // ok

    // printValue(5); // error: 5 is an rvalue
    printRef(5); // ok
}

Mixing Parameters

void foo(int a, int& b, const std::string& c) // mix parameters

Passing by Const Reference

Passing by const reference is better unless you want to change the referent.

Common Types to Pass by Value

Common Types to Pass by (const) Reference