In order to gives references about what other said see template argument deduction:
Before deduction begins, the following adjustments to P and A are
made:
- If P is not a reference type,
a) if A is an array type, A is
replaced by the pointer type obtained from array-to-pointer
conversion;
b) otherwise, if A is a function type, A is replaced by
the pointer type obtained from function-to-pointer conversion;
c) otherwise, if A is a cv-qualified type, the top-level cv-qualifiers
are ignored for deduction:
template<class T> void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
If P is a cv-qualified type,
the top-level cv-qualifiers are ignored for deduction.
If P is a
reference type, the referenced type is used for deduction.
If P is
an rvalue reference to a cv-unqualified template parameter (so-called
forwarding reference), and the corresponding function call argument is
an lvalue, the type lvalue reference to A is used in place of A for
deduction (Note: this is the basis for the action of std::forward
Note: in class template argument deduction, template parameter of a
class template is never a forwarding reference (since C++17)):
...
Basically, 1-a) means that trying to pass an array by value triggers array to pointer conversion (decay), loosing the static size information, while 3 says that passing by reference is keeping the original full type, with its size.
1-a)
By the way, the same seem to happen in non-template context (needing someone to provide a reference, maybe there: Array-to-pointer conversion).
Here is a possible illustration:
#include <iostream>
template <size_t N>
constexpr size_t Size(const char [N]) {
return N;
}
template <size_t N>
constexpr size_t Size2(const char (&)[N]) {
return N;
}
void Test(const char [5]) {
std::cout << "Passing array by value" << std::endl;
}
template<typename T>
void Test2(const T [3]) {
std::cout << "Template passing array by value" << std::endl;
}
void Testr(const char (&)[5]) {
std::cout << "Passing array by reference" << std::endl;
}
template<typename T>
void Testr2(const T (&)[3]) {
std::cout << "Template passing array by reference" << std::endl;
}
int main() {
// pointer to array decay, N cannot be deduced
// std::cout << Size("Test") << std::endl;
// reference to "sized" array, N can be deduced
std::cout << Size2("Test") << std::endl;
// also pointer to array decay, even in non template context, size provided in signature is not used
Test("Test");
Test("TestTest");
// pointer to array decay, size provided in signature is not used
Test2("Test");
Test2("TestTest");
Testr("Test");
// reference to "sized" array, size provided in signature is checked
// Testr("TestTest");
// Testr2("Test");
return 0;
}
Live Demo