18

I came across this snippet

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

in this article http://software.intel.com/en-us/articles/pvs-studio-vs-chromium/

I've seen other templates to do the same thing, like this one

Use templates to get an array's size and end address

and I understand those, but I've been having difficulty with this one.

Any help would be appreciated.

Community
  • 1
  • 1
BigSandwich
  • 2,768
  • 2
  • 22
  • 26
  • What in particular are you having difficulty with? There are lots of distinct elements of C++ at work here. – Lightness Races in Orbit Jun 16 '11 at 17:30
  • dupe. Also see the explanation at the bottom of this: http://stackoverflow.com/questions/437150/can-someone-explain-this-template-code-that-gives-me-the-size-of-an-array/437178#437178 (that's not the dupe, I'm too lazy to search it now). – Johannes Schaub - litb Jun 16 '11 at 17:56
  • Link to the dupe or it didn't happen :) I couldn't find the dupe either, otherwise I wouldn't have posted. – BigSandwich Jun 16 '11 at 18:24
  • and the real question why not to use `std::extent` instead of this ugly macro? – Gene Bushuyev Jun 16 '11 at 18:39
  • Possible duplicate of [Magic arguments in function templates](http://stackoverflow.com/questions/2384107/magic-arguments-in-function-templates) – Jonathan Mee Apr 25 '16 at 13:02
  • not sure why they want to complicate things.. Templates must be instantiated.. Macros dont and disappear into constants.. == sizeof(array)/Sizeof(element). #define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0])) – Dan May 24 '19 at 16:22

3 Answers3

11

The function template is named ArraySizeHelper, for a function that takes one argument, a reference to a T [N], and returns a reference to a char [N].

The macro passes your object (let's say it's X obj[M]) as the argument. The compiler infers that T == X and N == M. So it declares a function with a return type of char (&)[M]. The macro then wraps this return value with sizeof, so it's really doing sizeof(char [M]), which is M.

If you give it a non-array type (e.g. a T *), then the template parameter inference will fail.

As @Alf points out below, the advantage of this hybrid template-macro system over the alternative template-only approach is that this gives you a compile-time constant.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 1
    I'll just add about the purpose, namely to get the array size as a compile time constant (which in turn can be used to specify the size of a raw array). As I recall it was a Russian who first came up with this variation. Unfortunately I don't recall the name. – Cheers and hth. - Alf Jun 16 '11 at 17:35
  • So, I take it, that the function is declared but not defined? The syntax is hard for me to parse. – John Jun 16 '11 at 17:37
  • 1
    @Alf this is 2004, pretty early: http://blogs.msdn.com/b/the1/archive/2004/05/07/128242.aspx – Johannes Schaub - litb Jun 16 '11 at 17:58
  • Thanks, I think it was the syntax for returning char (&)[M] that was throwing me. C/C++ array syntax is awful. – BigSandwich Jun 16 '11 at 18:23
  • Johannes, that article is a perfect explanation. Thanks. – BigSandwich Jun 16 '11 at 18:23
  • `std::extent::value` is a compile-time constant, so what's the advantage? – Gene Bushuyev Jun 16 '11 at 18:41
  • 1
    @Gene Bushuyev: Basically that you can use this code in any compiler that follows the 98 standard, but you cannot use your approach unless the compiler implements C++0x features (at the very least `decltype` and the `extent` type trait. – David Rodríguez - dribeas Jun 16 '11 at 18:48
8

This isn't the nicest way of doing it, but since you're asking: The return type of the template function ArraySizeHelper is char[N], where the argument of the function is a (reference to an) array of size N of type T. Template argument deduction instantiates this template with the matching number N, and so sizeof(char[N]) is just N, which is what you get.

A nicer version could be written as follows. (You need C++0x for constexpr; if you omit it, this will not be a constant expression.)

template <typename T, size_t N> constexpr size_t array_size(const T (&)[N]) { return N; }

Usage:

int x[20];
array_size(x); // == 20

Update: If you are in C++0x, here is another solution that gives a constexpr, thanks to decltype:

#include <type_traits>

template <typename T> struct array_traits;
template <typename T, unsigned int N> struct array_traits<T[N]>
{
   static const unsigned int size = N;
   typedef std::decay<T>::type type;
};

// Usage:
int x[20];
array_traits<decltype(x)>::size; // == 20
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 2
    so out of curiosity, what do you feel the nicest way of doing it is? – jalf Jun 16 '11 at 17:35
  • 1
    @Jalf: Updated. No compiler macros! – Kerrek SB Jun 16 '11 at 17:39
  • 1
    Trouble with that is that it's not constexpr, unlike the macro. – Puppy Jun 16 '11 at 17:41
  • @DeadMG: True. Could we somehow wrap this in a way that doesn't need macros? More importantly, do the two compute different assembler output? – Kerrek SB Jun 16 '11 at 17:43
  • @Kerrek SB: the macro is only to simplify the syntax when using it. It's not *necessary* in any way (And the constexpr version pretty much by definition generates *no* assembler output – jalf Jun 16 '11 at 17:45
  • Hm, at `-O0` my version does generate some code, but at `-O2` the two produce the same result... yeah, the constexpr is morally better, though I find my oneliner syntactically more pleasant. Take your pick... – Kerrek SB Jun 16 '11 at 17:49
  • @Kerrek: yeah, yours is easily optimized away, but you're not guaranteed that this will happen, and more importantly, you can't use it in situations where a compile-time constant is expected, like you can with `sizeof`, or with the version the OP asked about. So that's definitely the more powerful version, but for most common uses, your "simple" version does the trick just fine (and it avoids macros) – jalf Jun 16 '11 at 18:33
  • @Jalf: I suppose, but in which situation would a compile-time constant be expected where you only have access to the object rather than its type? It's a good point, but I've been struggling to find examples for its use. If you have the type already, we can make a proper typetrait class anyway... – Kerrek SB Jun 16 '11 at 18:38
  • @Kerrek: in which case is `sizeof` necessary *at all* under those constraints? It is useful because the type might be a template parameter, so it is not explicitly stated, or because we don't want to duplicate the type information (which would be fragile if we later decide to change the size of the array). If you have the type, you don't need *any* kind of sizeof, and yet sizeof is useful. (And if you don't have the type, then no sizeof implementation will work, because that can only occur if the array has decayed to a pointer.) – jalf Jun 16 '11 at 18:59
  • But suppose you want to create a second array of the same size. The second array's size must be specified as a constant expression, so a `sizeof` trick like this would be a convenient way to do it, without having to type out the size information twice – jalf Jun 16 '11 at 19:01
  • @jalf: I added a C++0x version using `decltype` that's hopefully both constexpr _and_ macro free! – Kerrek SB Jun 16 '11 at 19:13
  • 1
    Just put `constexpr` before your first function template, and `array_size(x)` will be a constant expression too in C++0x. – Johannes Schaub - litb Jun 16 '11 at 19:26
  • @Johannes: Excellent! Does that mean that this is a completely equivalent solution to the macro now? [Update:] I just tested: Taken by itself at `-O0`, the assembly is different, but if I use `array_size(x)` as a template parameter, I get the exact same assembly as if I took the macro version. – Kerrek SB Jun 16 '11 at 19:29
4

This blog on MSDN precisely describes how it works. Very interesting story. Take a look at it.

Sergey Podobry
  • 7,101
  • 1
  • 41
  • 51