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
- 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 (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"
- two exceptions:
-
- "we can initialize a reference to
constfrom any expression that can be converted to the type of the reference" (see reference to const)
- "we can initialize a reference to
-
- two exceptions:
- 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 "
constreference", "lvalue reference to aconstvalue" X const& xis equivalent toconst X& x, andX const* xis equivalent toconst X* x. (see isocpp.org)- Unlike an ordinary reference, a reference to
constcannot be used to change the object to which the reference is bound - "we can initialize a reference to
constfrom any expression that can be converted to the type of the reference"- we can bind a reference to
constto- 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)
- we can bind a reference to
- "Binding a reference to
constto an object says nothing about whether the underlying object itself isconst."- ie. the underlying object cannot be changed by using the reference to
const, but it might be changed by other means
- ie. the underlying object cannot be changed by using the reference to
- 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
- usually refers to reference to const
From isocpp.org:
- references are always
const X& const xis functionally equivalent toX& x. Since you're gaining nothing by adding theconstafter the&, you shouldn't add it: it will confuse people - theconstwill make some people think that theXis const, as if you had saidconst X& x.
Classes Related By Inheritance
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 aBulk_quoteobject - assign the address of a
Bulk_quoteobject to aQuote*
- use a
- 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- see
f(T&& val)in Perfect Forwarding
- see
- it is mutable or immutable
constis 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 throughf()(orforwardToG()respectively) will be the same as if the code calledg()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
xwill always be an lvalue (recall: variables are lvalues) - solution: the
static_castcastsxto 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 ofstatic_castfor 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:
- To preserve type information in the arguments, we must define
forwardToG's function parameters as rvalue references to a template type parameterT&&. - We must use
forwardto preserve the arguments' original types whenforwardToGpasses those arguments tog.