16

This is just something that has bothered me for the last couple of days, I don't think it's possible to solve but I've seen template magic before.

Here goes:

To get the number of elements in a standard C++ array I could use either a macro (1), or a typesafe inline function (2):

(1)

#define sizeof_array(ARRAY) (sizeof(ARRAY)/sizeof(ARRAY[0]))

(2)

template <typename T>
size_t sizeof_array(const T& ARRAY){
    return (sizeof(ARRAY)/sizeof(ARRAY[0]));
}

As you can see, the first one has the problem of being a macro (for the moment I consider that a problem) and the other one has the problem of not being able to get the size of an array at compile time; ie I can't write:

enum ENUM{N=sizeof_array(ARRAY)};

or

BOOST_STATIC_ASSERT(sizeof_array(ARRAY)==10);// Assuming the size 10..

Does anyone know if this can be solved?

Update:

This question was created before constexpr was introduced. Nowadays you can simply use:

template <typename T>
constexpr auto sizeof_array(const T& iarray) {
    return (sizeof(iarray) / sizeof(iarray[0]));
}
Viktor Sehr
  • 12,825
  • 5
  • 58
  • 90
  • 2
    Possible duplicate? http://stackoverflow.com/questions/95500/can-this-macro-be-converted-to-a-function – Rob Sep 30 '09 at 20:52

10 Answers10

23

Try the following from here:

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

int testarray[10];
enum { testsize = mycountof(testarray) };

void test() {
    printf("The array count is: %d\n", testsize);
}

It should print out: "The array count is: 10"

Adisak
  • 6,708
  • 38
  • 46
  • Note -- for your two issues: #1) Macros : you do not have to use the countof() macro, you can just use sizeof(_ArraySizeHelper( array )) but the countof() macro is a simplification. #2) enums -- it works with enums in the above example – Adisak Sep 30 '09 at 20:39
  • 3
    +1. This has the added benefit of being typesafe (i.e., it will fail to compile if you pass a pointer instead of an array). – Josh Kelley Sep 30 '09 at 20:43
  • 3
    Its a nice solution, but not completely general: "it doesn’t work with types defined inside a function" – Georg Fritzsche Oct 21 '09 at 05:20
  • 2
    @gf: Templates in general don't work so well for types defined inside a function. In fact, it doesn't work for STL -- the following won't compile under GCC: void test() { struct foo{ int a; }; std::vector b; } You're much better off using a namespace (or anonymous namespace) if you need to create a class that is only going to be used by one function and you need to use templates on that class. – Adisak Oct 21 '09 at 16:21
  • @Adisak, @Georg: I think there is a way to get this to work for local types: http://stackoverflow.com/questions/1500363/compile-time-sizeof-array-without-using-a-macro/6256085#6256085 – Nate Kohl Jun 06 '11 at 17:58
19

In C++1x constexpr will get you that:

template <typename T, size_t N>
constexpr size_t countof(T(&)[N])
{
    return N;
}
dalle
  • 18,057
  • 5
  • 57
  • 81
8

The best I can think of is this:

template <class T, std::size_t N>
char (&sizeof_array(T (&a)[N]))[N];

// As litb noted in comments, you need this overload to handle array rvalues
// correctly (e.g. when array is a member of a struct returned from function),
// since they won't bind to non-const reference in the overload above.
template <class T, std::size_t N>
char (&sizeof_array(const T (&a)[N]))[N];

which has to be used with another sizeof:

int main()
{
    int a[10];
    int n = sizeof(sizeof_array(a));
    std::cout << n << std::endl;
}

[EDIT]

Come to think of it, I believe this is provably impossible to do in a single "function-like call" in C++03, apart from macros, and here's why.

On one hand, you will clearly need template parameter deduction to obtain size of array (either directly, or via sizeof as you do). However, template parameter deduction is only applicable to functions, and not to classes; i.e. you can have a template parameter R of type reference-to-array-of-N, where N is another template parameter, but you'll have to provide both R and N at the point of the call; if you want to deduce N from R, only a function call can do that.

On the other hand, the only way any expression involving a function call can be constant is when it's inside sizeof. Anything else (e.g. accessing a static or enum member on return value of function) still requires the function call to occur, which obviously means this won't be a constant expression.

Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • Remove the `const` to make it more generic. – dalle Sep 30 '09 at 20:45
  • Thanks for the tip. It will be able to deduce `T` as `const U` if it gets a `const U[N]&` as an argument, right? – Pavel Minaev Sep 30 '09 at 21:41
  • I like to use identity in these case, because it greatly improves readability: `template typename identity::type &sizeof_array(T const(&)[N]);` . For the `const` removal - this actually decreased genericity. Because now you cannot accept rvalue arrays anymore (non-const array reference won't bind to rvalue arrays): `struct A { int b[2]; }; .. sizeof_array(A().b); // doesn't work if it's not const!`. Notice that this rvalue array is not const itself, so deduction won't put const either. Note that this isn't a big issue - but arguably it's less generic i think. – Johannes Schaub - litb Sep 30 '09 at 22:55
  • The rvalue array isn't `const` itself, but its element type is `const`, so why wouldn't it be deduced? As a side note, both MSVC and (more reassuringly) Comeau are able to deduce `T` as `const int` when passing `const int a[10]` just fine. – Pavel Minaev Sep 30 '09 at 23:24
  • If the element type of an array is const, the array is const too, it propagates up (given `typedef int a[10];`, then `a const` is the same type as `int const[10]`) . In any case, surely with `int const a[10]; sizeof_array(a);` it works. But `a` is an lvalue, and its type is `int const[a]`, so `T` is `int const`. In case of `struct A { int b[2]; }; ... sizeof_array(getAnA().b);` the passed array is an rvalue, and its type is `int[2]`, so `T` is `int` - but non-const references won't bind to rvalues. Surely rvalue arrays are seldom, but nonetheless, genericity decreased instead of increased :) – Johannes Schaub - litb Sep 30 '09 at 23:31
  • Yeah, you're right regarding rvalue arrays... looks like this really needs two overloads to be fully generic. I'll add that. – Pavel Minaev Oct 01 '09 at 00:13
  • 1
    Note that it's fine for the `T const(&)[N]` to accept non-const arrays. Deduction will deduce `T` normally and will not get distracted by the additional const. It promotes use of `const` instead - wouldn't be good if deduction would force people to omit `const` if they want to accept non-const arguments. :) Anyway, upvoted you because of your nice analysis and sane naming (no insane underscores in front of names etc xD). – Johannes Schaub - litb Oct 01 '09 at 00:19
6

It's not exactly what you're looking for, but it's close - a snippet from winnt.h which includes some explanation of what the #$%^ it's doing:

//
// RtlpNumberOf is a function that takes a reference to an array of N Ts.
//
// typedef T array_of_T[N];
// typedef array_of_T &reference_to_array_of_T;
//
// RtlpNumberOf returns a pointer to an array of N chars.
// We could return a reference instead of a pointer but older compilers do not accept that.
//
// typedef char array_of_char[N];
// typedef array_of_char *pointer_to_array_of_char;
//
// sizeof(array_of_char) == N
// sizeof(*pointer_to_array_of_char) == N
//
// pointer_to_array_of_char RtlpNumberOf(reference_to_array_of_T);
//
// We never even call RtlpNumberOf, we just take the size of dereferencing its return type.
// We do not even implement RtlpNumberOf, we just decare it.
//
// Attempts to pass pointers instead of arrays to this macro result in compile time errors.
// That is the point.
//
extern "C++" // templates cannot be declared to have 'C' linkage
template <typename T, size_t N>
char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N];

#define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))

The RTL_NUMBER_OF_V2() macro ends up being used in the more readable ARRAYSIZE() macro.

Matthew Wilson's "Imperfect C++" book also has a discussion of the techniques that are used here.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • +1 : Reading the countOf code had me puzzled (I try not to spend much time mastering the obscure declaration rules C++ inherited from C). This answer provides with the exact info on what is exactly RtlpNumberOf. – paercebal May 16 '13 at 09:22
6

The Problem

I like Adisak's answer:

template <typename T, size_t N>
char ( &_ArraySizeHelper( T (&arr)[N] ))[N];
#define COUNTOF( arr ) (sizeof( _ArraySizeHelper( arr ) ))

It's what Microsoft uses for the _countof macro in VS2008, and it's got some nice features:

  • It operates at compile time
  • It's typesafe (i.e. it will generate a compile-time error if you give it a pointer, which arrays degrade too all too easily)

But as pointed out by Georg, this approach uses templates, so it's not guaranteed to work with local types for C++03:

void i_am_a_banana() {
  struct { int i; } arr[10];
  std::cout << COUNTOF(arr) << std::endl; // forbidden in C++03
}

Fortunately, we're not out luck.

The Solution

Ivan Johnson came up with a clever approach that wins on all accounts: it's typesafe, compile-time, and works with local types:

#define COUNTOF(arr) ( \
   0 * sizeof(reinterpret_cast<const ::Bad_arg_to_COUNTOF*>(arr)) + \
   0 * sizeof(::Bad_arg_to_COUNTOF::check_type((arr), &(arr))) + \
   sizeof(arr) / sizeof((arr)[0]) )

struct Bad_arg_to_COUNTOF {
   class Is_pointer; // incomplete
   class Is_array {};
   template <typename T>
   static Is_pointer check_type(const T*, const T* const*);
   static Is_array check_type(const void*, const void*);
};

For those who are interested, it works by inserting two "tests" before the standard sizeof-based array-size macro. Those tests don't impact the final calculation, but are designed to generate compile errors for non-array types:

  1. The first test fails unless arr is integral, enum, pointer, or array. reinterpret_cast<const T*> should fail for any other types.
  2. The second test fails for integral, enum, or pointer types.

    Integral and enum types will fail because there's no version of check_type that they match, since check_type expects pointers.

    Pointer types will fail because they'll match the templated version of check_type, but the return type (Is_pointer) for the templated check_type is incomplete, which will produce an error.

    Array types will pass because taking the address of an array of type T will give you T (*)[], aka a pointer-to-an-array, not a pointer-to-a-pointer. That means that the templated version of check_type won't match. Thanks to SFINAE, the compiler will move on to the non-templated version of check_type, which should accept any pair of pointers. Since the return type for the non-templated version is defined completely, no error will be produced. And since we're not dealing with templates now, local types work fine.

Community
  • 1
  • 1
Nate Kohl
  • 35,264
  • 10
  • 43
  • 55
  • It is no template implementation, but the best I've found so far. – To1ne Jul 20 '12 at 07:04
  • I like Ivan Johnson's idea, but I'm a bit confused. In C++11 there are simpler solutions, so let's assume that we don't have that. Then templates won't pick up local classes, so won't `localClass x; std::size_t length = COUNTOF(&x);` actually compile? (Sorry, I don't have a compiler that implements that old "feature" handy.) – Joshua Green May 08 '16 at 17:35
  • I may have been mistaken. Calling `COUNTOF(&x)` might lead to an _attempt_ to instantiate the `Is_pointer check_type` with `localClass` as the `template` parameter, perhaps causing a compile-time error (rather than just SFINAE). I can't tell if this was previously guaranteed by the standard or just unclear and assumed. – Joshua Green May 09 '16 at 01:46
  • I tested this on `g++`. In C++11 mode everything seemed to work correctly, but in non-C++11 mode `g++` was "happy" to compile `LocalType x; LocalType *ptr = &x; std::size_t length = COUNTOF(ptr);` with `length` getting, of course, a completely meaningless value. Whether this is an "extension," a valid interpretation of an ambiguous standard, or a bug is unclear to me, but regardless it seems that a completely type-safe version that handles local types is out of reach pre-C++11. – Joshua Green May 10 '16 at 02:08
4

If you are on a Microsoft only platform, you can take advantage of the _countof macro. This is a non-standard extension which will return the count of elements within an array. It's advantage over most countof style macros is that it will cause a compilation error if it's used on a non-array type.

The following works just fine (VS 2008 RTM)

static int ARRAY[5];
enum ENUM{N=_countof(ARRAY)};

But once again, it's MS specific so this may not work for you.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • _countof is implemented as a macro for C and a typesafe inline function for C++; in other words, it's identical to the question's (1) and (2). – Josh Kelley Sep 30 '09 at 20:36
3

You can't solve it in general, thats one the reasons for array wrappers like boost array (plus stl-style behaviour of course).

Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
  • -1: Wrong. Template metaprogramming can solve it, even before C++11. – Thomas Eding Aug 12 '14 at 00:20
  • @ThomasEding: Expand on how you would solve the two specific examples in the question without macros pre-C++11. The question originally was about how to solve them without doing macros. – Georg Fritzsche Aug 12 '14 at 11:22
  • Observe Nate Kohls answer for instance http://stackoverflow.com/a/6256085/239916. (He uses a macro, but that's just a convenience; it's not necessary.) I haven't tried, but I believe it should be possible and fairly straightforward to emulate `decltype` in C++03, thus you could write the equivalent of C++11's `std::extent::value, decltype(x)>::type>::value` in C++03. Yes, those traits don't exist natively in C++03, but those are definitely implementable. – Thomas Eding Aug 12 '14 at 15:21
  • That answer is interesting if it works properly with local types, but without macros that's effectively unusable (as in, you won't use it in practice). Emulating `decltype` in C++03 is not feasible. Also, reducing expressions like the above in C++03 to usable length/syntax sounds unrealistic. – Georg Fritzsche Aug 13 '14 at 13:04
  • @George: You're right. I thought certain things were possible to do in C++ (even in C++11) that are not possible to do at all when I tried writing some code for it. I can't remove my downvote apparently... wow. – Thomas Eding Aug 13 '14 at 16:38
  • Right, your votes get locked until the next edit, it's fine (i was more concerned about what i may have missed). – Georg Fritzsche Aug 14 '14 at 13:31
2

It appears not to be possible to obtain the sizeof array as a compile-time constant without a macro with current C++ standard (you need a function to deduce the array size, but function calls are not allowed where you need a compile-time constant). [Edit: But see Minaev's brilliant solution!]

However, your template version isn't typesafe either and suffers from the same problem as the macro: it also accepts pointers and notably arrays decayed to a pointer. When it accepts a pointer, the result of sizeof(T*) / sizeof(T) cannot be meaningful.

Better:

template <typename T, size_t N>
size_t sizeof_array(T (&)[N]){
    return N;
}
UncleBens
  • 40,819
  • 6
  • 57
  • 90
2

Without C++0x, the closest I can get is:

#include <iostream>

template <typename T>
struct count_of_type
{
};


template <typename T, unsigned N>
struct count_of_type<T[N]> 
{
    enum { value = N };
};

template <typename T, unsigned N>
unsigned count_of ( const T (&) [N] )
{
    return N;
};


int main ()
{
    std::cout << count_of_type<int[20]>::value << std::endl;
    std::cout << count_of_type<char[42]>::value << std::endl;

    // std::cout << count_of_type<char*>::value << std::endl; // compile error

    int foo[1234];

    std::cout << count_of(foo) << std::endl;

    const char* bar = "wibble";

    // std::cout << count_of( bar ) << std::endl; // compile error

    enum E1 { N = count_of_type<int[1234]>::value } ;

    return 0;
}

which either gives you a function you can pass the variable to, or a template you can pass the type too. You can't use the function for a compile time constant, but most cases you know the type, even if only as template parameter.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
  • With C++0x, you _can_ use a function for a compile time constant, if you declare it `constexpr` - as other answers have pointed out. – Pavel Minaev Sep 30 '09 at 21:39
  • Having read that post I put "Without C++0x" at the start of mine to show that I wasn't using that technique, but one which will work with current compilers. – Pete Kirkham Sep 30 '09 at 22:01
  • The compile-time constant version looks a bit pointless, though. Why would you ever go through the trouble of typing count_of_type::value if you have to know the size is 20? :) – UncleBens Sep 30 '09 at 22:12
  • You might want count_of_type::value where T is a template parameter, but I agree it's liable to be rare. – Pete Kirkham Oct 01 '09 at 07:40
2

Now STL libraries are available to decide/select array size compile time

#include <iostream>
#include <array>

template<class T>
void test(T t)
{
    int a[std::tuple_size<T>::value]; // can be used at compile time
    std::cout << std::tuple_size<T>::value << '\n';
}

int main()
{
    std::array<float, 3> arr;
    test(arr);
}

Output: 3

Hariom Singh
  • 3,512
  • 6
  • 28
  • 52