Templates and C-style array size

If you’re dealing with templates a lot in your C++ code, then you’re likely familiar with how template type deduction works. It’s an extensive topic which I’m not going to cover in detail here but while reading this book I found one aspect of it quite useful in my work.

Remember how you sometimes needed to know the size of C-style arrays? One way to determine it was doing something like this:

const int cArray[] = { 1, 2, 3, 4, 5 };

std::size_t aSize = sizeof(cArray) / sizeof(cArray[0]);

Nothing wrong with doing it this way but thanks to templates and the way they deduce data types we can get the array size in a much cleaner manner. To briefly recap on C-style arrays, even though you can specify a function signature using this syntax:

// how we may declare a function
void foo(int arr[])
{
}

What we essentially get is an implicit pointer conversion by the compiler:

// what compiler actually sees
void foo(int *arr)
{
}

This poses a problem, since there doesn’t seem to be an easy and obvious way to “extract” array size from the function variable. But there is a solution, one that employs templates. If you read up on how template type deduction works, you’ll learn that C-style array (and function pointers) is a special case treated differently depending on how you declare a template function:

– If a template function takes a non-reference and non-pointer parameter, deduced type for C-array is a pointer to its first element
– If a template function takes a reference parameter, deduced type for C-array is the actual array type

So in practice, what happens is:

const int cArray[] = { 1, 2, 3, 4, 5 };

template<typename T>
void foo(T arg)
{
}

template<typename T>
void foo2(T& arg)
{
}

foo(cArray);  // T will be deduced as int*
foo2(cArray); // T will be deduced as int[5]

The case of T &arg produces an interesting implication, since it allows us to directly access the size of an array from within the template function. For this, however, the argument has to be slightly modified:

// get C-style array size in a simple, painless way
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N]) noexcept
{
    return N;
} 

// Usage example:
const int cArray[] = { 1, 2, 3, 4, 5 };
std::size_t aSize = arraySize(cArray); // aSize is now 5

The constexpr keyword allow us to directly initialize static array sizes with the function’s result, while noexcept gives the compiler a chance for additional optimization. While this solution doesn’t help much when dealing with dynamically allocated arrays, it’s fun to know that C++ templates, usually regarded as code obfuscators, can make programming cleaner in certain applications.