How does this template code to get the size of an array work?

C++Visual C++

C++ Problem Overview


I wonder why this kind of code can get the size of the test array? I'm not familiar with the grammar in template. Maybe someone could explain the meaning of the code under template<typename,size_t>. Besides, a reference link is preferred too.

#define dimof(array) (sizeof(DimofSizeHelper(array)))
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

void InitDynCalls()
{
	char test[20];
	size_t n = dimof(test);
	printf("%d", n);
}

C++ Solutions


Solution 1 - C++

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 chars. 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.

Austin Powers Cross-Eyed


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.

Solution 2 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShadow fiendView Question on Stackoverflow
Solution 1 - C++BoBTFishView Answer on Stackoverflow
Solution 2 - C++bolovView Answer on Stackoverflow