Array decay to pointers in templates
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.
why does the array decay to a pointer in a template function
Because arrays can not be passed by value as a function parameter.
When you pass them by value they decay into a pointer.
In this function:
template <class T>
void f(T buff) {
T can not be char (&buff)[3]
as this is a reference. The compiler would have tried char (buff)[3]
to pass by value but that is not allowed. So to make it work arrays decay to pointers.
Your second function works because here the array is passed by reference:
template <class T>
void f1(T& buff) {
// Here T& => char (&buff)[3]
Why does array type not decay to pointer for class templates?
The "decay" you are talking about is something that happens when deducing a function template parameter from an argument. I have posted a fuller explanation here.
When you explicitly provide a value for a template parameter, there is no deduction step; the value you explicitly provided is exactly the value that the parameter takes.
With class templates, there is never parameter deduction; they must always have their parameters explicitly provided.
Illustrative examples:
template<typename T> void f(T t) {}
template<typename T> struct S { void f(T t) {} };
...
int x[27];
f(x); // Type deduction: decay occurs, T = int *
f<int *>(x); // No deduction. T = int *
f<int[27]>(x); // No deduction. T = int[27]
S<int[27]>().f(x); // No deduction. T = int[27]
In the latter two cases, adjustment still occurs. [temp.deduct]/3 explicitly restates this: when T
is an array type, the function parameter T t
means that t
has a pointer type, in exactly the same fashion that:
void g(int t[27])
actually specifies that t
has type int *
.
Array reference binding vs. array-to-pointer conversion with templates
The second overload is more specialized than the first one during partial ordering of function templates.
According to [temp.deduct.partial]/5 the reference on T &t
of the first overload is ignored during template argument deduction performed for partial ordering. The following paragraphs distinguish based on reference/value category only if both parameters are reference types.
Then T
of the first overload can always deduce against a type A*
invented from the parameter of the second overload, but T*
of the second overload can't deduce against a type A
invented from the parameter of the first overload.
Therefore the second overload is more specialized and is chosen.
With T (&t)[4]
argument deduction in both directions will fail because deduction of T[4]
against A*
will fail and so will deduction of T*
against A[4]
. Array-to-pointer decay of the array type is specified for template argument deduction for a function call but not for template argument deduction for partial ordering. See also active CWG issue 402.
So neither template will be more specialized in this case and the partial ordering tiebreaker does not apply.
The array-to-pointer conversion is not relevant. It is not considered any worse than the identity conversion sequence (see [over.ics.rank]/3.2.1 excluding lvalue transformations which array-to-pointer conversions are).
Why does pointer decay take priority over a deduced template?
The fundamental reason for this (standard-conforming) ambiguity appears to lie within the cost of conversion: Overload resolution tries to minimize the operations performed to convert an argument to the corresponding parameter. An array is effectively the pointer to its first element though, decorated with some compile-time type information. An array-to-pointer conversion doesn't cost more than e.g. saving the address of the array itself, or initializing a reference to it. From that perspective, the ambiguity seems justified, although conceptually it is unintuitive (and may be subpar). In fact, this argumentation applies to all Lvalue Transformations, as suggested by the quote below. Another example:
void g() {}
void f(void(*)()) {}
void f(void(&)()) {}
int main() {
f(g); // Ambiguous
}
The following is obligatory standardese. Functions that are not specializations of some function template are preferred over ones that are if both are otherwise an equally good match (see [over.match.best]/(1.3), (1.6)). In our case, the conversion performed is an array-to-pointer conversion, which is an Lvalue Transformation with Exact Match rank (according to table 12 in [over.ics.user]). [over.ics.rank]/3:
Standard conversion sequence
S1
is a better conversion sequence than standard conversion sequenceS2
if
S1
is a proper subsequence ofS2
(comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding
any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion
sequence) or, if not that,the rank of
S1
is better than the rank ofS2
, orS1
andS2
have the same rank and are distinguishable by the rules in the paragraph below, or, if not that,[..]
The first bullet point excludes our conversion (as it is an Lvalue Transformation). The second one requires a difference in ranks, which isn't present, as both conversions have Exact match rank; The "rules in the paragraph below", i.e. in [over.ics.rank]/4, don't cover array-to-pointer conversions either.
So believe it or not, none of both conversion sequences is better than the other, and thus the char const*
-overload is picked.
Possible workaround: Define the second overload as a function template as well, then partial ordering kicks in and selects the first one.
template <typename T>
auto foo(T s)
-> std::enable_if_t<std::is_convertible<T, char const*>{}>
{
std::cout << "raw, size=" << std::strlen(s) << std::endl;
}
Demo.
array decay to pointer and overload resolution
You need to make the first overload a poorer choice when both are viable. Currently they are a tie on conversion ranking (both are "Exact Match"), and then the tie is broken because non-templates are preferred.
This ought to make the conversion ranking worse:
struct stg
{
struct cvt { const char* p; cvt(const char* p_p) : p(p_p) {} };
// matches const char*, but disfavored in overload ranking
stg(cvt c_str); // use c_str.p inside :( Or add an implicit conversion
template<int N>
stg(const char (&str) [N]);
};
What is array to pointer decay?
It's said that arrays "decay" into pointers. A C++ array declared as int numbers [5]
cannot be re-pointed, i.e. you can't say numbers = 0x5a5aff23
. More importantly the term decay signifies loss of type and dimension; numbers
decay into int*
by losing the dimension information (count 5) and the type is not int [5]
any more. Look here for cases where the decay doesn't happen.
If you're passing an array by value, what you're really doing is copying a pointer - a pointer to the array's first element is copied to the parameter (whose type should also be a pointer the array element's type). This works due to array's decaying nature; once decayed, sizeof
no longer gives the complete array's size, because it essentially becomes a pointer. This is why it's preferred (among other reasons) to pass by reference or pointer.
Three ways to pass in an array1:
void by_value(const T* array) // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])
The last two will give proper sizeof
info, while the first one won't since the array argument has decayed to be assigned to the parameter.
1 The constant U should be known at compile-time.
array doesn't decay to pointer if passed by const reference in a template function
Array decay doesn't just happen -- it only happens when the program would fail to compile without it. When you pass an array by reference, there simply is no need for decay to kick in.
Note that the function template can also be written without dividing ugly sizeof
expressions:
template <typename T, std::size_t N>
std::size_t size_of_array(T (&array)[N])
{
return N;
}
When a client calls size_of_array
, T
and N
are automatically deduced by the template machinery.
Runtime sized arrays and pointer-decay
During template argument deduction, the array-to-pointer conversion is only used if the function template parameter's type is not a reference.
§14.8.2.1 Deducing template arguments from a function call
[temp.deduct.call]1 Template argument deduction is done by comparing each function
template parameter type (call itP
) with the type of the
corresponding argument of the call (call itA
) as described below.
[...]2 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; otherwise,- [...]
Related Topics
Stopping the Debugger When a Nan Floating Point Number Is Produced
Why Is Locking a Std::Mutex Twice 'Undefined Behaviour'
Why Would Anyone Use Set Instead of Unordered_Set
How to Call on a Function Found on Another File
How to Create a Type in C++ That Takes Less Than One Byte of Memory
Most Terse and Reusable Way of Wrapping Template or Overloaded Functions in Function Objects
Inserting into a Vector at the Front
Is Infinite Loop Still Undefined Behavior in C++ If It Calls Shared Library
How to Make Cout Behave as in Binary Mode
How to Implement Coroutines in C++
What the Heque Is Going on with the Memory Overhead of Std::Deque
Better Shading on Bw Display While Rendering Filled Surfaces