此模板代码如何获得数组的大小?

Sha*_*end 61 c++ visual-c++

我想知道为什么这种代码可以得到测试数组的大小?我对模板中的语法不熟悉。也许有人可以解释下代码的含义template<typename,size_t>。此外,参考链接也是首选。

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

Run Code Online (Sandbox Code Playgroud)

BoB*_*ish 86

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"
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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.

奥斯丁Powers斗眼


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.

  • 为什么它是函数引用的声明?“ DimofSizeHelper”之前的“&”表示返回类型为char(&)[N],根据bolov的回答。 (5认同)
  • @Shadowfiend这也是错误的。事情甚至比这更丑陋,因为它实际上不是函数的声明,而是函数引用的声明...我仍在弄清楚如何解释这一点。 (3认同)
  • @Shadowfiend绝对正确。我只是在说垃圾,因为我的大脑被打结了。 (3认同)

bol*_*lov 27

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];

Run Code Online (Sandbox Code Playgroud)

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));
Run Code Online (Sandbox Code Playgroud)

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; }
Run Code Online (Sandbox Code Playgroud)

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.

  • 从C ++ 11开始,我们就有了`std :: extent`,这是编译时间。 (3认同)
  • 如果不能ODR-使用要取其大小的数组(则已声明但未定义),则很有必要。诚然,相当晦涩。 (2认同)