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 |
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
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
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.
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
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
int var{};
int& ref1{ var }; // an lvalue reference bound to var
int& ref2{ ref1 }; // an lvalue reference bound to var
const int x { 5 }; // x is a non-modifiable lvalue
const int& ref { x }; // okay: ref is a lvalue reference to a const value
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
const int& ref { 5 }; // The temporary object holding value 5 has its lifetime extended to match ref
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
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
}
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
}
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
}
void foo(int a, int& b, const std::string& c) // mix parameters
Passing by const reference is better unless you want to change the referent.
std::string_view
std::string
std::array
std::vector