What is the type of a const reference?

862 views Asked by At

I know that passing a const reference variable to a function’s const reference parameter does not cause the function parameter to be of the type "const reference of a const reference of the referee's type". The variable name of the const reference argument is merely treated as another alias for the referee, but glorified with the protection that said alias cannot be used to modify the referee.

The idea of the reference variable's name being used as if it were an alias of the referee variable applies nicely to variables, giving another layer of indirection. It does not seem to make sense to apply analogous ideas of:

  • a const reference type itself being used as if it were an alias of its referee's type,
  • a typedef of a const reference type being used as an alias of its referee's type,
  • a const reference variable passed to (or deduced by) a template parameter gets its type interpreted as that of its referee's when the template parameter is typename T and its function parameter is T const&.

But that's what appears to happen in the following:

#include <typeinfo>
#include <iostream>

template <typename T>
T const& min(T const& a, T const& b) {
    std::cout << typeid(a).name() << std::endl;
    std::cout << typeid(b).name() << std::endl;
    return a < b ? a : b;
}

int main() {
    int x = 6, y = 7;
    int const& rx = x;
    std::cout << typeid(rx).name() << std::endl;           // "int"
    int z = ::min(rx, y);       //output shows both "a" and "b" are of type "int"
    std::cout << z << std::endl;                           // “6”
    typedef int const& icr;
    std::cout << typeid(icr).name() << std::endl;          // "int"
    std::cout << typeid(int const&).name() << std::endl;   // "int"
}
  1. Why does the function template work even for arguments that are already int const&? (In the sample code, it worked even for a call that had an int const& variable as the first parameter and an int variable as the second.) Shouldn’t it be invalid, because C++ does not allow a "reference of a reference"?

  2. Shouldn’t the typeid’s name() of int const& be int const&, instead of int?

  3. If not, then wouldn’t this mean int const& is an alias of int; which doesn’t make any sense, because both are different types (not names of variables)?

  4. Going back to variable names, given:

    int num = 8;
    int const& ref = num;
    std::cout << typeid(ref).name() << std::endl;
    

    why is the output int, and not int const&?

2

There are 2 answers

3
AnT stands with Russia On BEST ANSWER

Function arguments are expressions, not objects. Even if you supply a single object as a function argument, it is still an expression. The template argument deduction process will not work with the declared type of that object. It does not care about its exact declared type. What it cares about is the type of that expression.

Expressions in C++ never interpreted as having reference type. Every expression that physically has reference type is always interpreted as an lvalue (or an xvalue) of non-reference type. The "reference" part is immediately and irreversibly discarded and forgotten.

This is how it is worded in the standard (5/5)

5 If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

For example, in your code rx is declared as an int const &. However, every time you use rx as an expression, the result of that expression is always immediately adjusted from int const & to int const and is seen as an lvalue of int const type. In "expression result" context the language completely ignores the fact that rx was a reference.

It is often said that a reference can be thought of as just another name for an existing object. This is exactly the principle that works here. In "expression result" context, there's no difference between rx and x (aside from const-qualification).

Returning to your code, in this call

int z = ::min(rx, y);

the function arguments are interpreted as lvalues of int const and int type respectively. (I.e. the first argument is not really seen as having int const & type, contrary to your expectations). These are the types used for template argument deduction. Consequently, T is deduced as int. At no point in this process an attempt to produce a "reference-to-reference" has a chance to take place.

typeid works as you observe for exactly the same reason. typeid does not give you the declared type of the object. The argument of typeid in your case is an expression. typeid in this form gives you the type of the expression result, which is why typeid cannot "see" references. The const part is discarded because that's just how typeid works: top-level cv-qulifiers are always discarded by typeid.

1
uk4321 On
  1. Because references are collapsed in the context of type deduction, as are consts. Look up reference collapsing, there are many many answers on it already.

  2. No, because references are transparent, you can't look at them; when you name a variable that is a reference, the language always thinks you mean the referee. When you the typeid of a reference or const it ignores it.

  3. Avoid bolding unimportant words like variable, it makes your question hard to read :)