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:
typedef
of a const reference type being used as an alias of its referee's type,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"
}
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"?
Shouldn’t the typeid
’s name()
of int const&
be int const&
, instead of int
?
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)?
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&
?
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
.