How does this template code to get the size of an array work?
This is actually a really tough one to explain, but I'll give it a go...
Firstly, dimof
tells you the dimension, or number of elements in an array. (I believe "dimension" is the preferred terminology in Windows programming environments).
This is necessary because C++
and C
don't give you a native way to determine the size of an array.
Often people assume sizeof(myArray)
will work, but that will actually give you the size in memory, rather than the number of elements. Each element probably takes more than 1 byte of memory!
Next, they might try sizeof(myArray) / sizeof(myArray[0])
. This would give the size in memory of the array, divided by the size of the first element. It's ok, and widely used in C
code. The major problem with this is that it will appear to work if you pass a pointer instead of an array. The size of a pointer in memory will usually be 4 or 8 bytes, even though the thing it points to might be an array of 1000s of elements.
So the next thing to try in C++
is to use templates to force something that only works for arrays, and will give a compiler error on a pointer. It looks like this:
template <typename T, std::size_t N>
std::size_t ArraySize(T (&inputArray)[N])
{
return N;
}
//...
float x[7];
cout << ArraySize(x); // prints "7"
The template will only work with an array. It will deduce the type (not really needed, but has to be there to get the template to work) and the size of the array, then it returns the size. The way the template is written cannot possibly work with a pointer.
Usually you can stop here, and this is in the C++ Standard Libary as std::size
.
Warning: below here it gets into hairy language-lawyer territory.
This is pretty cool, but still fails in an obscure edge case:
struct Placeholder {
static float x[8];
};
template <typename T, int N>
int ArraySize (T (&)[N])
{
return N;
}
int main()
{
return ArraySize(Placeholder::x);
}
Note that the array x
is declared, but not defined. To call a function (i.e. ArraySize
) with it, x
must be defined.
In function `main':
SO.cpp:(.text+0x5): undefined reference to `Placeholder::x'
collect2: error: ld returned 1 exit status
You can't link this.
The code you have in the question is a way around that. Instead of actually calling a function, we declare a function that returns an object of exactly the right size. Then we use the sizeof
trick on that.
It looks like we call the function, but sizeof
is purely a compile time construct, so the function never actually gets called.
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];
^^^^ ^ ^^^
// a function that returns a reference to array of N chars - the size of this array in memory will be exactly N bytes
Note you can't actually return an array from a function, but you can return a reference to an array.
Then DimofSizeHelper(myArray)
is an expression whose type is an array on N
char
s. The expression doesn't actually have to be runable, but it makes sense at compile time.
Therefore sizeof(DimofSizeHelper(myArray))
will tell you the size at compile time of what you would get if you did actually call the function. Even though we don't actually call it.
Don't worry if that last block didn't make any sense. It's a bizarre trick to work around a bizarre edge case. This is why you don't write this sort of code yourself, and let library implementers worry about this sort of nonsense.
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];
// see it like this:
// char(&DimofSizeHelper(T(&array)[N]))[N];
// template name: DimofSizeHelper
// param name: array
// param type: T(& )[N])
// return type: char(& )[N];
DimofSizeHelper
is a template function which takes a T(&)[N]
parameter - aka a reference to a C-array of N elements of type T
and returns a char (&)[N]
aka a reference to an array of N chars. In C++ a char is byte in disguise and sizeof(char)
is guaranteed to be 1
by the standard.
size_t n = dimof(test);
// macro expansion:
size_t n = sizeof(DimofSizeHelper(array));
n
is assigned the size of the return type of DimofSizeHelper
, which is sizeof(char[N])
which is N
.
This is a bit convoluted and unnecessary. The usual way to do it was:
template <class T, size_t N>
/*constexpr*/ size_t sizeof_array(T (&)[N]) { return N; }
Since C++17 this also is unnecessary, as we have std::size
which does this, but in a more generic way, being able to get the size of any stl-style container.
As pointed out by BoBTFish, it is necessary for an edge case.