Tuesday 6 December 2011

Obtaining the size of a C++ array using templates and other techniques (from C++11) - Part 2

Welcome back!  In Part 1 we saw a nifty technique to obtain the size of an array via function template argument deduction.  However, this ended with the problem of how to use this value at compile time, e.g. as the size of another array.  This proves hard in C++98 but C++11 changes things.

The simplest way to enable compile time use is to make use of the new constexpr keyword.  This tells the compiler that the function can be completely evaluated (to a constant value) at compile.  As such C++11 allows this function to be called at compile time though instead the evaluated constant value is used.  Adding this to the definition of GetNumberOfElements as below

template<typename T, size_t sizeOfArray>
constexpr size_t GetNmberOfElements(T (&)[sizeOfArray])
{
  return sizeOfArray;
}


now allows the following to compile

char a[100];
char b[GetNumberOfElements(a)];


Is this the best that we can do?  Using raw array types isn't that good.  Even though the size of the array is known and can now be discovered at compile time using it in std::for_each is somewhat burdensome as the length must be obtained in order to obtain the end iterator, i.e.

std::for_each(&a[0], &a[GetNumberOfElements(a)], SomeFunctor());

Due to the raw array not being an object it doesn't have an end method which means the code must rely on the programmer specifying the correct index for the end of an array or using the technique above.

This can be remedied by moving from raw array types to std::array (first made available in TR1), e.g.

std::array<char, 100> a;
std::for_each(std::begin(a), std::end(a), SomeFunctor());

(Note the use of the C+11 non-member versions begin and end functions)

The downside of using std::array is that even though it can be initialized when defined, e.g.

std::array<int, 4> a = { 0, 1, 2, 3 };

It effectively requires the length of the array specifying twice.  Firstly as the template parameter and then again implicitly by the number of elements in the initializer list.

Given the new initializer list feature of C++11 it could be expected that this would allow initialization without the length parameter.  However it seems this is not supported "out of the box" though there are some interesting techniques to allow this.

All is not lost though.  Taking a step back and returning to the raw array the size issue can be circumvented by use of C++11's range-for statement.

char a[100];
SomeFunctor sf;
for (auto x : a)
sf(x);

To backtrack once again, the use of the non-member versions of begin and end can also be used on raw arrays so the example above can also be written as:

char a[100];
std::for_each(std::begin(a), std::end(a), SomeFunctor());

Which if the purpose of the loop is to invoke a functor then std::for_each is more succinct than range-for as it doesn't require an explicit instance of the functor.

I suspect the same technique for finding the length of a naked array along with constexpr is used by the non-member version of end to obtain the length.  In fact one version of end is probably a function template overloaded to accept a raw array as its container parameter, e.g.

template<typename T, size_t>
constexpr T* example_begin(T (&array)[])
{
  return &array[0];
}


template<typename T, size_t sizeOfArray>
constexpr T* example_end(T (&array)[sizeOfArray])
{
  return &array[sizeOfArray];
}


char a[100];
std::for_each(example_begin(a), example_end(a), SomeFunctor());

However, any looping mechanism that requires both the start and end to be specified rather than just the container does allow for an error to occur by having begin and end of different containers specified whereas range-for eliminates this possibility!

In summary, it's possible to find the size of a raw array using function template deduction and using C++11 features that value can be used at compile time.  However, if the reason that the size needs obtaining is to obtain the end iterator then C++11 makes this redundant through the use of range-for and the non-member versions of begin and more accurately end.