Please consider this code:
#include <iostream>
template<typename T>
void f(T x) {
std::cout << sizeof(T) << '\n';
}
int main()
{
int array[27];
f(array);
f<decltype(array)>(array);
}
Editor's Note: the original code used typeof(array)
, however that is a GCC extension.
This will print
8 (or 4)
108
In the first case, the array obviously decays to a pointer and T becomes int*
. In the second case, T is forced to int[27]
.
Is the order of decay/substitution implementation defined? Is there a more elegant way to force the type to int[27]
? Besides using std::vector?
Use the reference type for the parameter
template<typename T> void f(const T& x)
{
std::cout << sizeof(T);
}
in which case the array type will not decay.
Similarly, you can also prevent decay in your original version of f
if you explicitly specify the template agument T
as a reference-to-array type
f<int (&)[27]>(array);
In your original code sample, forcing the argument T
to have the array type (i.e. non-reference array type, by using typeof
or by specifying the type explicitly), will not prevent array type decay. While T
itself will stand for array type (as you observed), the parameter x
will still be declared as a pointer and sizeof x
will still evaluate to pointer size.
The behaviour of this code is explained by C++14 [temp.deduct.call]:
Deducing template arguments from a function call
Template argument deduction is done by comparing each function template parameter type (call it
P
) with the type of the corresponding argument of the call (call itA
) as described below
and then below:
If
P
is not a reference type:
- If
A
is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is used in place ofA
for type deduction;
For the call f(array);
, we have A
= int[27]
. A
is an array type. So the deduced type T
is int *
, according to this last bullet point.
We can see from the qualifier "If P
is not a reference type" that this behaviour could perhaps be avoided by making P
a reference type. For the code:
template<typename T, size_t N>
void f(T (&x)[N])
the symbol P
means T(&)[N]
, which is a reference type; and it turns out that there are no conversions applied here. T
is deduced to int
, with the type of x
being int(&)[N]
.
Note that this only applies to function templates where the type is deduced from the argument. The behaviour is covered by separate parts of the specification for explicitly-provided function template parameters, and class templates.
You can also use templates like the following:
template <typename T, std::size_t N>
inline std::size_t number_of_elements(T (&ary)[N]) {
return N;
}
This little trick will cause compile errors if the function is used on a non-array type.
Depending on your use case, you can work around that using references:
template<typename T>
void f(const T& x) {
std::cout << sizeof(T);
}
char a[27];
f(a);
That prints 27
, as desired.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With