Why does a const reference to a reference lose its constness?
Given this code:
#include <iostream>
template<typename T>
void modify(const T &j){ j = 42; } // j has type int&
int main()
{
int i = 10;
modify<int&>(i); // T=int&
std::cout << i; // 42 is printed
}
Why does const T &j
become int &j
if T=int&
? What happens to const
?
There is no such thing as reference to a reference i.e. there is no T & &
.
Given a const T&
where T
is int&
, the type collapses into int&
.
What happens to const?
There is no such thing as a const reference either i.e. there is no T & const
(not to be confused with reference to const, which does exist and which is quite often colloquially called const reference). No reference can be modified (which would be different from modifying the referred object), so constness is not meaningful. The const is simply ignored here.
Standard rule (from latest draft):
[dcl.ref] If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR. [ Note: This rule is known as reference collapsing. — end note ]
Here cv refers to cv-qualifiers i.e. const and volatile.
To clarify why this applies to template arguments:
[temp.param] A type-parameter whose identifier does not follow an ellipsis defines its identifier to be a typedef-name ...
P.S. The way reference collapsing is specified is part of the reason why perfect forwarding works.
Given const T&
, const
is qualified on T
itself. When T
is int&
, const T
means const
reference (i.e. int& const
); there's no const
reference in fact, (note that it's not reference to const
i.e. const int&
), references can't be rebound after being initialized. In this case the const
qualifier is just ignored.
Reference types cannot be cv-qualified at the top level; there is no syntax for that in declaration, and if a qualification is added to a typedef-name or decltype specifier, or type template parameter, it is ignored.