computer science goes bonk / all posts / rss / about

making COUNTOF suck less

If you've played around with C++ enough, you've probably run across something like this:

#define COUNTOF(arr) (sizeof(arr) / sizeof(arr[0]))

It's our good friend COUNTOF, a cute little macro that helps us figure out how big arrays are:

int primes[] = {2, 3, 5, 7, 11, 13};

for (int i = 0; i < COUNTOF(primes); ++i) {
   std::cout << primes[i] << std::endl;
}

At first glance, this is great: COUNTOF is like a magic incantation that lets us avoid the highly error-prone world of C-style arrays.

The Problem

Unfortunately, COUNTOF can fail in a pretty spectacular way.   It's kind of ridiculous, really:

#define COUNTOF(arr) (sizeof(arr) / sizeof(arr[0]))

void foo(int *primes) {
   // someone said that arrays and pointers are pretty much the same,
   // so this should work, right?
   std::cout << COUNTOF(primes) << std::endl;
}

void bar(int primes[]) {
   // okay, maybe that last one won't be right, but this one has got to work!
   std::cout << COUNTOF(primes) << std::endl;
}

int main() {
   int primes[] = {2, 3, 5, 7, 11, 13};
   std::cout << COUNTOF(primes) << std::endl; // fine, displays "6"
   foo(primes);  // displays e.g. "4", aka sizeof(int*)
   bar(primes);  // also "4"
}

The problem is that C++ allows arrays to decay into pointers at the drop of a hat.  This is useful behavior, because it allows us to do fun things with pointers without having to do a lot of casting, but it also allows for some pretty unexpected behavior.

One Solution

Wouldn't it be nice if the compiler would warn us when we try to pass the wrong type of data to COUNTOF? Check this out: 

template <typename T, size_t N>
inline size_t countof(const T (&arr)[N]) {
   return N;
}

void foo(int primes[]) {
   // compiler error: primes is not an array
   std::cout << countof(primes) << std::endl;
}

int main() {
   int primes[] = {2, 3, 5, 7, 11, 13};
   std::cout << countof(primes) << std::endl; // fine, displays "6"
   foo(primes);
}

This approach uses some template trickery to define a free function that takes a reference to an array of N elements of type T.  Since we're using an array explicitly, the compiler will error out if we try to pass a pointer into this function.  And that's good: the compiler is our friend, and we should be happy when it tells us that we're doing something wrong.

But I like macros!

Maybe you're worried about performance. Maybe you just don't trust the inline keyword.  Is there a way we can do all of this at compile time?

template <typename T, size_t N>
char (&ArraySizeHelper( T (&arr)[N] ))[N];
#define COUNTOF(arr) ( sizeof(ArraySizeHelper(arr)) )

void foo(int primes[]) {
   // compiler error: primes is not an array
   std::cout << COUNTOF(primes) << std::endl;
}

int main() {
   int primes[] = {2, 3, 5, 7, 11, 13};
   std::cout << COUNTOF(primes) << std::endl; // fine, displays "6"
   foo(primes);
}

What the heck is that?

This version of COUNTOF uses a free helper function called ArraySizeHelper that is declared, but not defined.  Like the inline function from our first solution, this helper function takes a reference to an array of N objects of type T.  This function, however, returns a reference to an array of N objects of type char.  (What?!  You can return raw arrays from functions in C++?  Sure, as long as you return them by reference.)

We don't need to define ArraySizeHelper, because we never call it: arguments to the sizeof operator are not evaluated.  The only thing that sizeof uses is type information, which in this case is the return type of ArraySizeHelper.

We could define COUNTOF like so:

...
#define COUNTOF(arr) ( sizeof(ArraySizeHelper(arr)) / sizeof(char) )

...but since sizeof(char) is defined as 1, we can omit it.

This version of COUNTOF won't accidentally compile when passed pointers and runs completely at compile-time. This is good. In fact, this is how the _countof macro in Visual Studio (VC9) is defined.

But Wait, There's More!

This template tomfoolery is all well and good, but there are places where templates won't work well.  Locally-defined types are useful when you want to define a "throwaway" type that won't be used outside of the current function, but like that old joke goes, local types and templates don't mix. Or maybe I'm thinking of a different joke.

...
void foo() {
   struct {int i; bool b;} arr[10];
   // compiler error: template argument uses local type
   std::cout << COUNTOF(arr) << std::endl;
}

(Update: this problem was fixed in C++11.)

Ivan Johnson solved this problem with a clever COUNTOF macro that wins on all accounts: it's typesafe, compile-time, and works with local types:

#define COUNTOF(arr) ( \
   0 * sizeof(reinterpret_cast<const ::Bad_arg_to_COUNTOF*>(arr)) + \
   0 * sizeof(::Bad_arg_to_COUNTOF::check_type((arr), &(arr))) + \
   sizeof(arr) / sizeof((arr)[0]) )

struct Bad_arg_to_COUNTOF {
   class Is_pointer; // incomplete
   class Is_array {};
   template <typename T>
   static Is_pointer check_type(const T*, const T* const*);
   static Is_array check_type(const void*, const void*);
};

And I'll bet you thought that last version was cryptic.

This version isn't as bad as it first looks.  It works by inserting two "tests" before the standard sizeof-based array-size macro. Those tests don't impact the final calculation, but are designed to generate compile errors for non-array types:

  1. The first test fails unless arr is integral, enum, pointer, or array.  reinterpret_cast should fail for any other types.
  2. The second test fails for integral, enum, or pointer types. Integral and enum types will fail because there's no version of check_type that they match, since check_type expects pointers. Pointer types will fail because they'll match the templated version of check_type, but the return type (Is_pointer) for the templated check_type is incomplete, which will produce an error. Array types will pass because taking the address of an array of type T will give you T (*)[], aka a pointer-to-an-array, not a pointer-to-a-pointer. That means that the templated version of check_type won't match. Thanks to SFINAE, the compiler will move on to the non-templated version of check_type, which should accept any pair of pointers. Since the return type for the non-templated version is defined completely, no error will be produced. And since we're not dealing with templates now, local types work fine.

So there you have it: a COUNTOF macro that is fast, safe, and helps you avoid those pernicious pointer problems.