Implementation
The design goals for COUNTOF were to:
- Preserve the good features of the traditional COUNTOF.
- Present users with a type-safe interface.
- Generate error messages that at least hint at the problem when an inappropriate argument is passed.
COUNTOF achieves those goals, but the required implementation is a bit obscure (Listing One). In pseudocode, it is simply:
if x is not an array issue a compile-time error else use the traditional COUNTOF expression
Probably the best way to understand how COUNTOF does this is to break it into parts. The macro expands to an expression that can be summarized like this:
0 * sizeof(check1) + 0 * sizeof(check2) + sizeof(x) / sizeof((x)[0])
The first thing to note is that the first two sizeof expressions are not really being used to compute the size of anything. Because they are both multiplied by zero, they do not make any contribution to the result. They are just convenient vehicles for some type checks to verify that the argument is an array.
The check1 expression reinterpret_casts the macro argument x to a pointer. The cast is never actually executed; it is just part of a compile-time check. If the argument is an object of class type, such as an std::vector, then the cast is not legal and the compiler issues an error. The same thing happens if the argument is a floating-point type, a function pointer, or a pointer-to-member. Any pointer to an object would work as the result type, but I chose Bad_arg_to_COUNTOF* (and named the class that way) because the result type is likely to show up in any compiler error messages, and that name gives users a hint at what the problem is.
If the reinterpret_cast succeeds, then x must be an integral or enumerated type, a pointer to an object, or an array. The compiler moves on to the check2 clause. This part of the macro expands approximately to sizeof(check_type(x, &x)), where check_type is an overloaded function [1]. Because this is purely a compile-time computation, the function is never really called or even implemented, but it lets the compiler apply overload resolution to do some further type discrimination. There are three possibilities to consider:
- x is an integral or enumerated type. In this case, neither of the two overloads is a match, and the compiler issues an error.
- x is a pointer to an object. If x is a pointer, then the first argument of check_type is a pointer and the second one is a pointer-to-pointer. The best match is the first overload of check_type, the one that returns an Is_pointer. Because Is_pointer is an incomplete type, sizeof(Is_pointer) is not a valid expression, and again the compiler issues an error.
- x is an array. If x is an array, then the first argument to check_type is an array and the second is a pointer-to-array. A pointer-to-array is not convertible to a pointer-to-pointer, so the first overload of check_type is not a match. However, an array is convertible to a pointer, and a pointer-to-array already is a pointer. Any pointer is convertible to a void*, so the second overload is a match. That overload returns an Is_array, which is a complete type for which sizeof(Is_array) is a valid expression.
At this point, the compiler has excluded every possible type except arrays. The first two lines have reduced to zeros, and the compiler moves on to the third. That line is just sizeof(x)/sizeof((x)[0]); that is, the size in bytes of the entire array divided by the size in bytes of a single element, which yields the number of elements in the array. This is the same expression used by the traditional COUNTOF, and it gives the same result.
Conclusion
The new COUNTOF macro preserves all the benefits of the traditional COUNTOF, while adding the type safety that macros often lack. Its only real disadvantage is the cryptic code needed to implement it. However, because the complexity is hidden behind a simple and well-defined interface that exactly matches the original, it is not a serious problem.
Acknowledgment
Thanks to Andrew Hopkins for his thoughtful review of a draft of this article.
References
- [1] The method of eliminating pointers with sizeof and a pair of overloaded functions is based on one devised by Jonathan Lundquist. See groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/bd3b7cc6e26d3ebe.
- [2] The countof and count function templates are adapted from ones by Dietmar Kuehl and Siemel Naran, respectively. See groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/d0faf979c493c19c and groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/8e45b21773e191ea.