Arrays as Function Arguments
Suppose you wanted to declare a function that takes an array of a specific size as its argument. For example, you might want a function to compute the distance of a point from the origin. A first attempt might look something like this:
double dist(const double x[3]); Unfortunately, the compiler discards the size you supplied and interprets the function as one taking a const double* as its argument, so that calling it with any size of array is syntactically legal (but probably gives unexpected results at runtime). If you really want the compiler to enforce that the function receives an array of exactly three elements, you can use an array reference (as shown below). Then you can write and call the function just as you would expect:
double dist(const double(&x)[3]) { return sqrt( x[0] * x[0] + x[1] * x[1] + x[2] * x[2] ); } double a[] = { 3, 4, 5 }; double d = dist(a); // OK double b[] = { 3, 4, 5, 6 }; double d2 = dist(b); // error I.J.J. |
countof Without Macros
The COUNTOF in Listing One requires a macro for readability, but it is possible to write a readable countof function using just templates [2]. The first function in Listing Five shows one way. You could call it like this:
// ... a[] is some array ... int ct = countof(a); for ( int i = 0; i != ct; ++i ) /* ... process a[i] ... */ ;
The function works, but it has two disadvantages: First, the return value cannot be used as a constant expression; and second, the function won't compile for local types.
// returns the number of elements in the passed array template<typename T, int N> inline int countof(const T(&)[N]) { return N; } // a struct defined such that sizeof(Size<N>::Type) == N template<int N> struct Size { typedef char Type[N]; }; // returns an object whose size is the number of // elements in the passed array template<typename T, int N> typename Size<N>::Type& count(const T(&array)[N]);
Listing Five
A constant expression is an expression that can be evaluated at compile time and used where the compiler expects a true constant, such as in array bounds, case labelsor for countof, compile-time assertions. The C++ rules governing constant expressions are very strict about what they can contain, and function calls are not allowed. If you need the count as a compile-time constant, this creates a quandary: You need to call a function to count the elements in the array. Having counted them, you need some way to get the result back out of the function, but you can't use the return value to get it without losing its constancy. Fortunately, the rules have a loophole.
Function calls cannot appear directly in constant expressions, but they can be used as arguments to sizeof, and the result of sizeof can, in turn, be used as a constant expression. The solution, then, is for the function to be a template that indicates the array size not by its return "value," but by its return "type." This works because finding the count is actually a compile-time operation, so the result can be used as a template parameter of the return type. The function returns an object whose size, as determined by sizeof, equals the number of elements in the array. The balance of Listing Five implements this strategy. The count function returns a Size<N>::Type object, which is defined in such a way that sizeof(Size<N>::Type) == N. Therefore, to get the number of elements in a[] as a compile-time constant, you would write sizeof(count(a)).
The other limitation of the template is with local types. These types lack external linkage, and templates cannot be instantiated on types without external linkage. The only workaround is to put the type definition in a different scope where it is no longer local. This is always possible, but less than ideal. When a program uses a simple type in only one function, it is useful to be able to define the type right there in the function.
Overall, the problems with the template solution are that the sizeof(count()) notation is a little strange for getting a compile-time constant, and that the templates can't be used on local types. More generally, a tool should not make you bend your program to accommodate it; instead, it should accommodate you. To my knowledge, all pure template-based countof solutions have these same problems. I prefer a solution that "just works," for all types, with a consistent, intuitive notation, so in this case, I prefer the macro in Listing One.
I.J.J. |