This is a quirk/feature in C++. Although we don't think of references as types, they in fact "sit" in the type system. Although this seems awkward (given that when references are used, the reference semantics occurs automatically and the reference "gets out of the way"), there are some defensible reasons why references are modeled in the type system instead of as a separate attribute outside of type.
Firstly, let us consider that not every attribute of a declared name must be in the type system. From the C language, we have "storage class", and "linkage". A name can be introduced as extern const int ri
, where the extern
indicates static storage class and the presence of linkage. The type is just const int
.
C++ obviously embraces the notion that expressions have attributes that are outside of the type system. The language now has a concept of "value class" which is an attempt to organize the growing number of non-type attributes that an expression can exhibit.
Yet references are types. Why?
It used to be explained in C++ tutorials that a declaration like const int &ri
introduced ri
as having type const int
, but reference semantics. That reference semantics was not a type; it was simply a kind of attribute indicating an unusual relationship between the name and the storage location. Furthermore, the fact that references are not types was used to rationalize why you cannot construct types based on references, even though the type construction syntax allows it. For instance, arrays or pointers to references not being possible: const int &ari[5]
and const int &*pri
.
But in fact references are types and so decltype(ri)
retrieves some reference type node which is unqualified. You must descend past this node in the type tree to get to the underlying type with remove_reference
.
When you use ri
, the reference is transparently resolved, so that ri
"looks and feels like i
" and can be called an "alias" for it. In the type system, though, ri
does in fact have a type which is "reference to const int
".
Why are references types?
Consider that if references were not types, then these functions would be considered to have the same type:
void foo(int);
void foo(int &);
That simply cannot be for reasons which are pretty much self-evident. If they had the same type, that means either declaration would be suitable for either definition, and so every (int)
function would have to be suspected of taking a reference.
Similarly, if references weren't types, then these two class declarations would be equivalent:
class foo {
int m;
};
class foo {
int &m;
};
It would be correct for one translation unit to use one declaration, and another translation unit in the same program to use the other declaration.
The fact is that a reference implies a difference in implementation and it is impossible to separate that from type, because type in C++ has to do with the implementation of an entity: its "layout" in bits so to speak. If two functions have the same type, they can be invoked with the same binary calling conventions: the ABI is the same. If two structs or classes have the same type, their layout is the same as well as the semantics of access to all the members. The presence of references changes these aspects of types, and so it's a straightforward design decision to incorporate them into the type system. (However, note a counterargument here: a struct/class member can be static
, which also changes the representation; yet that isn't type!)
Thus, references are in the type system as "second class citizens" (not unlike functions and arrays in ISO C). There are certain things we cannot "do" with references, such as declare pointers to references, or arrays of them. But that doesn't mean they aren't types. They just aren't types in a way that it makes sense.
Not all these second-class-restrictions are essential. Given that there are structures of references, there could be arrays of references! E.g.
// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };
// ar[0] is now an alias for x: could be useful!
This just isn't implemented in C++, that's all. Pointers to references do not make sense at all, though, because a pointer lifted from a reference just goes to the referenced object. The likely reason why there are no arrays of references is that the C++ people consider arrays to be a kind of low-level feature inherited from C that is broken in many ways that are irreparable, and they don't want to touch arrays as the basis for anything new. The existence of arrays of references, though, would make a clear example of how references have to be types.
Non-const
-qualifiable types: found in ISO C90, too!
Some answers are hinting at the fact that references don't take a const
qualifier. That is rather a red herring, because the declaration const int &ri = i
isn't even attempting to make a const
-qualified reference: it's a reference to a const-qualified type (which is itself not const
). Just like const in *ri
declares a pointer to something const
, but that pointer is itself not const
.
That said, it is true that references cannot carry the const
qualifier themselves.
Yet, this is not so bizarre. Even in the ISO C 90 language, not all types can be const
. Namely, arrays cannot be.
Firstly, the syntax doesn't exist for declaring a const array: int a const [42]
is erroneous.
However, what the above declaration is trying to do can be expressed via an intermediate typedef
:
typedef int array_t[42];
const array_t a;
But this doesn't do what it looks like it does. In this declaration, it is not a
which gets const
qualified, but the elements! That is to say, a[0]
is a const int
, but a
is just "array of int". Consequently, this doesn't require a diagnostic:
int *p = a; /* surprise! */
This does:
a[0] = 1;
Again, this underscores the idea that references are in some sense "second class" in the type system, like arrays.
Note how the analogy holds even more deeply, since arrays also have an "invisible conversion behavior", like references. Without the programmer having to use any explicit operator, the identifier a
automatically turns into an int *
pointer, as if the expression &a[0]
had been used. This is analogous to how a reference ri
, when we use it as a primary expression, magically denotes the object i
to which it is bound. It's just another "decay" like the "array to pointer decay".
And just like we must not become confused by the "array to pointer" decay into wrongly thinking that "arrays are just pointers in C and C++", we likewise mustn't think that references are just aliases that have no type of their own.
When decltype(ri)
suppresses the usual conversion of the reference to its referent object, this is not so different from sizeof a
suppressing the array-to-pointer conversion, and operating on the array type itself to calculate its size.