Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 14.  Arrays and Pointers


14.3. dimensionof()

The definition of dimensionof() (in the form of NUM_ELEMENTS()) given in section 14.1 relies on textual substitution by the preprocessor, and as such it contains a serious flaw. If we apply it to a pointer, to which we now know we can apply subscripts, the substituted code will be wrong.



int     ar[10];


int     *p  = ar; // or = &ar[0]


size_t  dim = NUM_ELEMENTS(p); // sizeof(int*) / sizeof(int) !



The result yielded will be the integer division of the size of a pointer to int divided by the size of int, which would be 1 on most platforms. This is wrong (except in the case where the dimension of the array to which p points happens to be 1) and quite misleading.

Just as bad is the case where the operator is applied to a user-defined type with an accessible index operator (operator []). In such a case, the size yielded can be anything from 0 where the size of the instance is larger than the type it returns from the index operator, to a large number where that size relationship is reversed. It is even conceivable that the number yielded can be correct during an interactive debugging session, giving an entirely false sense of security to the author of the code! Far better for the compiler to balk at our application of the (pseudo-)operator and refuse to compile the expression. Thankfully, if we take the advice of section 14.2.1, and use the reversed form of NUM_ELEMENTS(), this problem goes away. But even if your equivalent macro does this—and most don't—we've still got the problem with pointers, since they can work just as well as an array name in either subscript operator form.

Imperfection: Inability to distinguish between arrays and pointers can result in static array size determination being applied to pointers or class types, yielding incorrect results.


The answer here is to distinguish between arrays and pointers. Until recently this was not possible, but most modern compilers support a technique that can do so. It relies on the fact that array-to-pointer conversion (decay) does not occur in the resolution of template arguments that are of reference type (C++-98: 14.3.2; [Vand2003, p58]). Thus we can define a macro—let's call it dimensionof()—with the same definition as NUM_ELEMENTS() for older compilers, but for modern ones use the following combination of a macro, a structure, and a function:



template <int N>


struct array_size_struct


{


  byte_t  c[N];


};





template <class T, int N>


array_size_struct<N> static_array_size_fn(T (&)[N]);





#define dimensionof(x)     sizeof(static_array_size_fn(x).c)



Basically it declares, but does not define, a template function static_array_size_fn() that takes a reference to an array of type T and dimension N, its two template parameters. That gets rid of the pointers and user-defined types, but it's not yet got us to dimensionof(). The function returns an instance of the template structure array_size_struct, which is parameterized on the dimension passed to static_array_size_fn() and contains an array of bytes of that dimension. The macro dimensionof() simply applies the sizeof() operator to the array within the returned structure instance, thereby yielding the dimension of the array.[5]

[5] Note that compilers are free to layout members of structures according to whatever criteria they choose, so a full implementation must surround the definition of array_size_struct with an appropriate packing pragma(s), such as #pragma pack(1), to be certain that the structure size is equal to N.

A dimensionof() expression (whether NUM_ELEMENTS() or our new dimensionof() macro + function) is evaluated at compile time, and so may be used wherever any other constant value can, for example, in template parameters, in array dimensions, enum values, and so on. Because the standard (C++-98: 5.3.3) states that the operand to a sizeof() operator is not evaluated, there is no need to define static_array_size_fn(), so all this utility comes at absolutely no price. There's no run time cost, and no code bloat; in fact there's no code generated at all! Attempting to apply dimensionof() to any type other than an array causes a compilation error.



int         ai[23];


int         *pi = ai;


vector<int> vi(23);





size_t cai = NUM_ELEMENTS(ai); // Ok


size_t cpi = NUM_ELEMENTS(pi); // Compiles, but it is wrong!


size_t cvi = NUM_ELEMENTS(vi); // Compiles, but it is wrong!





cai = dimensionof(ai); // Ok


cpi = dimensionof(pi); // Error – good!


cvi = dimensionof(vi); // Error – good!



I should point out there's a slightly shorter (albeit harder to decipher) way to implement dimensionof(), as follows:



template<typename T, int N>


byte_t (&byte_array_of_same_dimension_as(T (&)[N]))[N];





#define dimensionof(x)      sizeof(byte_array_of_same_dimension_as((x)));



Unfortunately this is recognized by fewer compilers[6] so I recommend the first form.

[6] Visual C++ 7.0 recognizes the first but not the second.


      Previous section   Next section