Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the type of a built-in array's size?

When deducing a non-type template parameter, say n, the types of n must match exactly between the parameter and argument. So the following code will not compile (at least on GCC and clang):

#include <array>
template <int n> void f(std::array<int, n> const&) {}
int main() {
  std::array<int, 3> arr;
  f(arr);
}

This is because std::array is declared as

template <typename T, std::size_t n> class array;

rather than

template <typename T, int n> class array;

However, to capture the size of a built-in array, it seems that any integral type does. All of the following work on GCC, clang, and VC++:

template <typename T, char n > void f(T (&)[n]);
template <typename T, short n> void f(T (&)[n]);
template <typename T, int   n> void f(T (&)[n]);
...

So, seriously, the type of a built-in array's size is overloaded?

like image 966
Lingxi Avatar asked Jul 18 '14 13:07

Lingxi


2 Answers

From §8.3.4 [dcl.array]/1:

If the constant-expression (5.19) is present, it shall be a converted constant expression of type std::size_t and its value shall be greater than zero. The constant expression specifies the bound of (number of elements in) the array

The type is std::size_t.

The reason other types work in general is that conversions are done on the type passed in according to §14.3.2 [temp.arg.nontype]/5:

For a non-type template-parameter of integral or enumeration type, conversions permitted in a converted constant expression (5.19) are applied.

From §5.19 [expr.const]/3:

An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression.

snip (mentions these can be used as array bounds)

A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, where the converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4).

Finally, §4.7 [conv.integral]/3:

If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

If the value of the bound fits inside of your parameter type, it will be converted successfully. If not, you'll end up with an implementation-defined value for the bound.

Arrays are a special case, as pointed out in ectamur's answer:

§14.8.2.5 [temp.deduct.type]/17:

If, in the declaration of a function template with a non-type template-parameter, the non-type template-parameter is used in an expression in the function parameter-list and, if the corresponding template-argument is deduced, the template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral type.

like image 91
chris Avatar answered Nov 10 '22 14:11

chris


This is covered in 14.8.2.5 [temp.deduct.type] paragraph 17:

17 - [...] [The] template-argument type shall match the type of the template-parameter exactly, except that a template-argument deduced from an array bound may be of any integral type.

In http://wg21.cmeerw.net/cwg/issue1770 this is improved to the more general:

17 - If P has a form that contains <i>, and if the type of the corresponding value of A differs from the type of i, deduction fails. If P has a form that contains [i], and if the type of i is not an integral type, deduction fails.

So array bounds can be deduced to any integral type, but non-type template parameters must be deduced to the actual type in the template definition.

Indeed, there is nowhere in the C++11 version of the Standard that specifies a preferred type for array bounds; an array bound is specified (in [dcl.array]) to be "an integral constant expression and its value shall be greater than zero". In recent drafts for C++14 this is amended to "a converted constant expression (5.19) of type std::size_t [...]"; the changed definition can be traced to n3306. Somewhat oddly, the change is presented as a "consequential adjustment [..] for the sake of consistency", implying that the editors considered it self-apparent that size_t was the correct type.

like image 4
ecatmur Avatar answered Nov 10 '22 15:11

ecatmur