Let's consider those definitions:
/*** full type information with typeid ***/
template <class> class Type{};
template <class T> std::string typeStr()
{ return typeid(Type<T>).name(); }
/*** function template for parameter deduction ***/
template <class T> void func(const T &a)
{
std::cout << "Deduced type for T is: " << typeStr<T>() << std::endl;
std::cout << "\targument type is: " << typeStr<decltype(a)>() << std::endl;
}
with pointers to const
If the following statements are executed:
const int i=5, *ip=&i;
func(ip);
The output is:
Deduced type for T is: 4TypeI**PKi**E
So T
is actually deduced as a pointer to a constant integer. The fact that the argument is a reference-to-const does not change the deduction, which is what one would expect because the constness of the pointer is low-level.
but with array of const
Nonetheless, if following statements are executed:
const int ia[3] = {3, 2, 1};
func(ia);
The output is:
Deduced type for T is: 4TypeI**A3_i**E
So T
is actually deduced as an array of 3 non-const integers. The fact that the argument is a reference-to-const does change the deduction, as if the const
was slipping into the array elements.
Actually, versions of CL up to 18 were deducing T
as array of 3 const integers was what I expected to be standard, but it seems that since v19 it converged to what GCC and Clang are doing (i.e., deducing as non-const).
Thus, I assume the later behaviour to be standard, but was is the rationale ? It could seem surprising that it does not behave like with pointers.
Edit: Following dip comment, I will report here pointers to CWG issues related to this behaviour, pointers he actually posted as a comment on this answer (answer that actually raised this new question... C++ feels like a deep tunnel)
Using this function template prototype:
In your first example, the type deduction works as:
Note: This is pseudocode. The full type is
const int* const&
.Note that the
const int
remainsconst int
, but the*
becomes* const
.This is because
const int*
is just a regular, mutable, non-volatile pointer. It is just a*
. What it points to is irrelevant.But in the second example, you have:
Note: This is pseudocode. The real type would be
const int (&a)[3]
.So the type deduction is working the same in both cases, discarding the outer
const
.It so happens that a
const
array is the same as an array ofconst
elements.It might help to write types like this:
On that second example, the
const
appears to "move" from being applied on the array to being applied on the elements. This is because you can't really have aconst
array, only an array ofconst
elements.