64

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);
}

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Shadow fiend
  • 753
  • 5
  • 7
  • Did you read something like [n3337](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf) about [C++11](https://en.wikipedia.org/wiki/C%2B%2B11) ? It should be relevant to your question ! Did you consider using [`std::array`](https://en.cppreference.com/w/cpp/container/array) or [`std::vector`](https://en.cppreference.com/w/cpp/container/vector) .... – Basile Starynkevitch Oct 16 '19 at 08:13
  • @BasileStarynkevitch I have not read that. The code appears in a third-party library. I just want to figure out the meaning. – Shadow fiend Oct 16 '19 at 08:25
  • See also http://norvig.com/21-days.html for a useful insight (and look who is the author of that page). – Basile Starynkevitch Oct 16 '19 at 08:39
  • 2
    Looks like a duplicate of https://stackoverflow.com/questions/6106158/how-does-sizeof-work-in-this-helper-for-determining-array-size – sharptooth Oct 17 '19 at 12:23
  • @BasileStarynkevitch I don't understand the relevance of that link. – Lightness Races in Orbit Oct 17 '19 at 17:36

2 Answers2

89

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.

BoBTFish
  • 19,167
  • 3
  • 49
  • 76
  • 3
    @Shadowfiend It's also wrong. Things are even uglier than that, because it's not actually a declaration of a function, it's a declaration of a function reference... I'm still figuring out how to possibly explain that. – BoBTFish Oct 16 '19 at 08:13
  • 5
    Why it's a declaration of a function reference? The "&" before "DimofSizeHelper" means the return type is char(&)[N], accoring to bolov's answer. – Shadow fiend Oct 16 '19 at 08:32
  • 3
    @Shadowfiend Absolutely right. I was just talking rubbish because I got my brain tied in a knot. – BoBTFish Oct 16 '19 at 08:36
  • Dimension is not the number of elements in an array. That is, you might have 1, 2, 3, or higher dimensioned arrays, which could each have the same number of elements. E.g. array1D [1000], array 2D [10][100], array3D [10][10][10]. each having 1000 elements. – jamesqf Oct 16 '19 at 19:25
  • @Nelson but you do write it for *some* good reason, you write it once, and most likely never modify it. – hobbs Oct 16 '19 at 20:47
  • 1
    @jamesqf In languages like C++, a multidimensional array is simply an array which contains other arrays. From the compiler's point of view, the number of elements in the primary array is often completely unrelated to its contents -- which may be secondary or tertiary arrays. – Phlarx Oct 16 '19 at 21:26
  • I swear, that picture could apply to half the questions in the C++ tag – Hong Ooi Oct 17 '19 at 01:28
  • @Phlarx: WRT C++, maybe yes, maybe no, depending on whether you're talking of a flat array or an array of pointers to arrays &c. (One reason I almost always compute my own indices into 1D arrays for multi-dimensional things.) WRT English and math, dimension is what I said. – jamesqf Oct 17 '19 at 03:43
  • @jamesqf It's hardly unusual in our industry, but yes "dimension" is an abused term here. I don't like to use it for this purpose, but the reality is that lots of people do. To be fair "size" isn't great here either, as it can be ambiguous whether you mean "number of elements" or "size in memory". I dunno, maybe we can get "cardinality" to catch on? – BoBTFish Oct 17 '19 at 05:57
  • It seems more likely to me that the dance with the return type and `sizeof` is to produce a compile-time constant. The code was probably written in C++98 where you can't just slap a `constexpr` on the function if you want to use the returned size as the size of a new array or what have you. – chris Oct 17 '19 at 06:26
  • @BoBTFish: Perhaps it's that I've mainly worked in scientific & engineering areas, where the distinction is always made. – jamesqf Oct 18 '19 at 02:25
  • Once again; the devil's in details. – Red.Wave Oct 18 '19 at 18:39
29
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.

bolov
  • 72,283
  • 15
  • 145
  • 224