int x = 5;
int &ref1 = x; // okay, x is an non-const l-value
const int y = 7;
int &ref2 = y; // not okay, y is a const l-value
int &ref3 = 6; // not okay, 6 is an r-value
Note that in the middle case, you can’t initialize a non-const reference with a const object -- otherwise you’d be able to change the value of the const object through the reference, which would violate the const-ness of the object.
The primary downside of using non-const references as function parameters is that the argument must be a non-const l-value. This can be restrictive.
To recap, l-values are objects that have a defined memory address (such as variables), and persist beyond a single expression. r-values are temporary values that do not have a defined memory address, and only have expression scope. R-values include both the results of expressions (e.g. 2 + 3) and literals.
References vs pointers:
References and pointers have an interesting relationship -- a reference acts like a pointer that is implicitly dereferenced when accessed (references are usually implemented internally by the compiler using pointers).Because references must be initialized to valid objects (cannot be null) and can not be changed once set, references are generally much safer to use than pointers (since there’s no risk of dereferencing a null pointer). However, they are also a bit more limited in functionality accordingly.
If a given task can be solved with either a reference or a pointer, the reference should generally be preferred. Pointers should only be used in situations where references are not sufficient (such as dynamically allocating memory).