4

I was playing with C++ template type deduction and managed to compile this little program.

template<typename T>
 void f(const T& val)
 {
   val = 1;
 }


 int main()
 {
    int i = 0;
    f<int&>(i);
 }

It compiles on all major compilers but I do not understand why. Why can f assign to val, when val is explicitly marked const? Is it bug in these compilers or is it valid behavior according to the C++ standard?

Geoff Romer
  • 2,358
  • 1
  • 18
  • 19
  • Reference collapsing. – T.C. Dec 02 '14 at 18:39
  • 1
    @πάνταῥεῖ That doesn't seem right to me. An lvalue reference to an lvalue reference is an lvalue reference, not an rvalue reference, so it should expand to `void f(const int&)`. – Geoff Romer Dec 02 '14 at 18:42

3 Answers3

6

§8.3.2 [dcl.ref]/p6:

If a typedef-name (7.1.3, 14.1)* or a decltype-specifier (7.1.6.2) 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.

This is known as reference collapsing and is introduced in C++11. In C++03, your code would be ill-formed, but some compilers supported it as an extension.

Note that in const T &, the const applies to the type T, so when T is int &, the const would apply to the reference type itself (which is meaningless as references are immutable anyway), not the type referred to. This is why the reference collapsing specification ignores any cv-qualifiers on TR.


*A template type parameter is a typedef-name, per §14.1 [temp.param]/p3:

A type-parameter whose identifier does not follow an ellipsis defines its identifier to be a typedef-name (if declared with class or typename) or template-name (if declared with template) in the scope of the template declaration.

T.C.
  • 133,968
  • 17
  • 288
  • 421
2

I would like to append other posts with the following quote from the C++ Standard (8.3.2 References)

1... Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef-name (7.1.3, 14.1) or decltype-specificer (7.1.6.2), in which case the cv-qualifiers are ignored.

[ Example:
typedef int& A;
const A aref = 3; // ill-formed; lvalue reference to non-const initialized with rvalue

In the example above from the C++ Standard the last statement will not be compiled because qualifier const is ignored (according to the quote) and you may not bind rvalue with a non-const reference.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • This seems to be the relevant quote. –  Dec 02 '14 at 19:06
  • The "ignoring cv-qualifier" part is embedded into the specification for reference collapsing and doesn't require the extra paragraph you cited. Note the "cv TR" part. – T.C. Dec 02 '14 at 22:25
  • @T.C. I could say in turn that the part of specification for reference collapsing relative to cv-qualifiers is described in the quote I cited. So there is ni need in extra paragraph you referenced to because the question in essence about cv-qualifiers of references that are template arguments. – Vlad from Moscow Dec 03 '14 at 05:42
1

const T& does not turn into int& in this example: in f<int&>(i), you are explicitly setting T to int&. const only enters the picture when that definition of T is used to determine the type of val, which becomes const int& (one subtlety there is that references are collapsed during this substitution, so you don't wind up with two &'s in the type).

Geoff Romer
  • 2,358
  • 1
  • 18
  • 19