8

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?

M.M
  • 138,810
  • 21
  • 208
  • 365
Gunther Piez
  • 29,760
  • 6
  • 71
  • 103
  • 3
    Where did you find a C++ compiler where `sizeof(int) == 1`? I get 108 for the second call. – Rob Kennedy Dec 08 '09 at 00:12
  • Yes, of course gcc has already moved up to 4 byte ints ;-) I introduced a bug while generating a testcase. – Gunther Piez Dec 08 '09 at 00:29
  • I'm surprised the second call compiles. You can't pass arrays by value in C++. [edit: Ah, `T` has the array type, but `sizeof(x)` would still output 8 or 4. Never mind. :)] – Lightness Races in Orbit May 09 '11 at 22:00
  • Nice necro comment :-) Are you just browsing my old questions? :-) The second case is passing the array as reference, that's what I didn't recognize when I asked. See the accepted answer – Gunther Piez May 09 '11 at 22:12

4 Answers4

10

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
4

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 it A) 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 of A 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.

M.M
  • 138,810
  • 21
  • 208
  • 365
1

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.

Community
  • 1
  • 1
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
1

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.

D.Shawley
  • 58,213
  • 10
  • 98
  • 113