Reference declaration

< cpp‎ | language
Revision as of 07:10, 29 October 2013 by (Talk)

C++ language
General topics
Flow control
Conditional execution statements
Iteration statements
Jump statements
function declaration
lambda function declaration
function template
inline specifier
exception specifications (deprecated)
noexcept specifier (C++11)
decltype specifier (C++11)
cv specifiers
storage duration specifiers
constexpr specifier (C++11)
auto specifier (C++11)
alignas specifier (C++11)
alternative representations
typedef declaration
type alias declaration (C++11)
attributes (C++11)
implicit conversions
const_cast conversion
static_cast conversion
dynamic_cast conversion
reinterpret_cast conversion
C-style and functional cast
Memory allocation
Class-specific function properties
Special member functions
class template
function template
template specialization
parameter packs (C++11)
Inline assembly

Declares a named variable as a reference, that is, an alias to an already-existing object or function.



A reference variable declaration is any simple declaration whose declarator has the form

& attr(optional) declarator (1)
&& attr(optional) declarator (2) (since C++11)
1) Lvalue reference declarator: the declaration S & D; declares D as an lvalue reference to the type determined by decl-specifier-seq S.
2) Rvalue reference declarator: the declaration S && D; declares D as an rvalue reference to type determined by decl-specifier-seq S.
declarator - any declarator except another reference declarator (there are no references to references)
attr(C++11) - optional list of attributes

A declaration of a reference (except for class members and function parameters) is required to contain an initializer: see reference initialization which must identify a valid object or function.

There are no references to void and no references to references.

Reference types cannot be cv-qualified at the top level; there is no syntax for that in declaration, and if a qualification is introduced through a typedef, it is ignored.

References are not objects; they do not necessarily occupy storage, although the compiler may allocate storage if it is necessary to implement the desired semantics (e.g. a non-static data member of reference type usually increases the size of the class by the amount necessary to store a memory address)

Because references are not objects, there are no arrays of references, no pointers to references, and no references to references:

int& a[3]; // error
int&* p; // error
int& &r; // error

However, it is permitted to form references to references through type manipulations in templates or typedefs, in which case the reference collapsing rules apply: rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference:

typedef int&  lref;
typedef int&& rref;
int n;
lref&  r1 = n; // type of r1 is int&
lref&& r2 = n; // type of r2 is int&
rref&  r3 = n; // type of r3 is int&
rref&& r4 = 1; // type of r4 is int&&

(this is part of the rules that make std::forward possible)

Lvalue references

Lvalue references can be used to alias an existing object (optionally with different cv-qualification)

#include <iostream>
#include <string>
int main()
    std::string s = "Ex";
    std::string& r1 = s;
    const std::string& r2 = s;
    r1 += "ample"; // modifies s
//  r2 += "!"; // error: cannot modify through reference to const
    std::cout << r2 << '\n'; // prints s, which now holds "Example"

They can also be used to implement pass-by-reference semantics in function calls:

#include <iostream>
#include <string>
void double_string(std::string& s)
    s += s; // 's' is the same object as main()'s 'str'
int main()
    std::string str = "Test";
    std::cout << str << '\n';

When a function's return type is lvalue reference, the function call expression becomes an lvalue expression:

#include <iostream>
#include <string>
char& char_number(std::string& s, std::size_t n)
    return; // string::at() returns a reference to char
int main()
    std::string str = "Test";
    char_number(str, 1) = 'a'; // the function call is lvalue, can be assigned to
    std::cout << str << '\n';

Rvalue references

Rvalue references can be used to extend the lifetime of a modifiable temporary (note, lvalue references to const can extend lifetimes too, but they are not modifiable)

#include <iostream>
#include <string>
int main()
    std::string s1 = "Test";
//    std::string&& r1 = s1; // error: can't bind to lvalue
    const std::string& r2 = s1 + s1; // OK, lvalue ref to const extends lifetime
//    r2 += "Test"; // error: can't modify through reference to const
    std::string&& r3 = s1 + s1; // OK, rvalue ref extends lifetime
    r3 += "Test"; // this rvalue can be modified
    std::cout << r3 << '\n';

More importantly, when a function has both rvalue reference and lvalue reference overloads, the rvalue reference overload binds to rvalues, while the lvalue reference overload binds to lvalues.

#include <iostream>
void f(int& x)
     std::cout << "lvalue reference overload f(" << x << ")\n";
void f(const int& x)
     std::cout << "lvalue reference to const overload f(" << x << ")\n";
void f(int&& x)
     std::cout << "rvalue reference overload f(" << x << ")\n";
int main()
    int n = 1;
    const int x = 2;
    f(n); // calls f(int&)
    f(x); // calls f(const int&)
    f(3); // calls f(int&&)
          // would call f(const int&) if f(int&&) overload wasn't provided

This allows move constructors, move assignment operators, and other move-aware functions (e.g. vector::push_back() to be automatically selected when suitable.

Dangling references

Although references, once initialized, always refer to valid objects or functions, it is possible to create a program where the life time of the referred-to object ends, but the reference remains accessible (dangling). Accessing such reference is undefined behavior. A common example a function returning a reference to an automatic variable:

std::string& f() {
    std::string str = "Example";
    return str; // exits the scope of str: str's destructor is called
}               // stack storage deallocated
std::string& r = f(); // r is a dangling reference
std::cout << r;      // Undefined: reads from a dangling reference
std::string s = f(); // Undefined: uses a dangling reference to copy-initialize s

Note that rvalue references and lvalue references to const extend the lifetimes of temporary objects, see reference_initialization for rules and exceptions.

If the referred-to object was destroyed (e.g. by explicit destructor call), but the storage was not deallocated, the following uses of the reference are undefined:

1) lvalue to rvalue conversion (e.g. function call to a function that takes a value)
2) access to a non-static data member or a call to a non-static member function
3) implicit conversion to a reference to a base class subobject
4) static_cast to anything other than char& or unsigned char&
5) dynamic_cast or typeid expressions

If an object is recreated at the same memory location (e.g. by placement new), the reference becomes valid once again, if all of the following is true:

1) the storage occupied by the new object exactly overlays the storage occupied by the old object
2) the new object has the same type as the old object, ignoring top-level cv-qualifiers
3) the original object's type was not const-qualified
4) the original object was not a class with const or reference non-static data members
5) both the original and the new objects are the most-derived objects of their type

Note, the above rules apply to pointers as well (except that pointers to storage without an object can be additionally cast to void*)