The C++ logo, by Jeremy Kratz, licensed under CC0 1.0 Universal Public Domain Dedication

Query the number of elements in an array

Notes published the
5 - 6 minutes to read, 1298 words

C and C++, both languages, lack a fool-proof method for querying the number of elements of an array at compile-time

Consider the following array

int array[] = {1,2,3,4};

or

int array[4] = {0,};

In both cases, there is an array with four elements, but there is no operator for querying this information at runtime, or compile-time.

sizeof

In C, the only method that I know that can be used for querying the number of elements is the operator sizeof.

sizeof takes as a parameter a type or expression and returns the size in bytes.

Thus sizeof(array) would return how big the array is, but not the number of elements.

To get the number of elements, one needs to divide the size of the array by the size of a single element, and since an array always has at least one element, one straightforward way to calculate the size is to write

sizeof(array)/sizeof(array[0])

Since it is cumbersome to write the name of the variable more than once, it is not uncommon to see a macro similar to the following

#define ARR_SIZE(array) (sizeof(array)/sizeof(array[0]))

Unfortunately, this expression works both for arrays and pointers, and since arrays decay to pointers very quickly, it is very desirable to have a compile-time error if ARR_SIZE is used on a pointer.

The only portable solution I’ve found requires at least C23, and uses _Generic, typeof, and static_assert.

// returns true if array
#define IS_ARRAY(A) \
  _Generic( &(A), \
    typeof(*A) (*)[]: true, \
    default         : false \
  )

// static_assert with value 1
#define STATIC_ASSERT_EXPR(EXPR, MSG) \
  (!!sizeof( struct { static_assert( (EXPR), MSG ); int i; } ))


#define ARRAY_SIZE(array) \
  ((sizeof(array)/sizeof(array[0])) * STATIC_ASSERT_EXPR( IS_ARRAY(array), #array " is not an array" ))

If you are working with an older standard, maybe your compiler has some extension that can help writing something similar.

std::size

If you can use C++, you should know that you do not need to resort to something like ARRAY_SIZE, as it is possible to provide a function template that can return the size of an array, and fails to compile if the argument is not an array:

template<class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept
{
  return N;
}

as the function is constexpr, it can be used at compile-time too. You do not even need to write such code, the standard library has provided this function since C++17, but if you are working with an older standard, just copy and paste the previous code snippet.

Another advantage of a function named size is that it can be overloaded and specialized. The std::size function works with std::vector, std::set, and all other standard containers, and always returns the number of elements that the containers currently have, although at runtime.

std::size at compile time with function parameters

Unfortunately, templated functions have some limitations that macros do not have.

Consider the following snippet of code

int array[] = {1,2,3,4};
static_assert(std::size(array) == 4);

it compiles successfully, but the following does not

void foo(int (&array1)[4]) {
  int array2[] = {1,2,3,4};
  static_assert(std::size(array1) == 4); // fails to compile
  static_assert(std::size(array2) == 4); // compiles
}

static_assert(std::size(array1) == 4); fails to compile, because array1 is of type int (&)[4], while static_assert(std::size(array2) == 4); compiles as array2 is of type int[4].

std::size(array1), if the result is not needed at compile time, compiles without errors, but it does not help if one wants to query the size at compile time.

This issue, and a proposed solution, have been presented in the paper Using unknown pointers and references in constant expressions 🗄️, thus from C++26 this code should compile with all compilers.

Note 📝
the provided snippet seems to compile successfully with all GCC versions I’ve tested.

In the meantime, if you need to target other compilers, you might want to resort to sizeof, but see the end of the notes on how to write a type-safe macro

void foo(int (&array1)[4]) {
  int array2[] = {1,2,3,4};
  static_assert(std::size(array2) == 4); // compiles
  static_assert(sizeof(array1)/sizeof(array1[0]) == 4); // compiles
}

std::size and member variables

Another place where std::size does not work well is with member variables.

Consider the following snippet

struct data{
  data();
  int array[4];
  static constexpr int v1 = std::size(array); // fails to compile
  static           int v2 = std::size(array); // fails to compile
                   int v3 = std::size(array); // fails to compile
  static constexpr int v4 = sizeof(array)/sizeof(array[0]); // compiles
};
static_assert(std::size(data::array) == 4); // fails to compile
static_assert(sizeof(data::array)/sizeof(data::array[0]) == 4); // compiles

In this example, even removing constexpr does not help; to get the size of int array[4];, we need a valid data object, even if we want to only query the type, not the content.

This is generally not acceptable, especially in this case, as the constructor of data is not constexpr, otherwise one could write

static_assert(std::size(data().array) == 4);

On the other hand, sizeof works without issues.

Thus even when Using unknown pointers and references in constant expressions 🗄️ is implemented in all compilers, there will be at least this scenario, where someone might want to resort to sizeof

A type-safe ARRAY_SIZE in C++

Why not copy the C implementation?

Because C++ does not have _Generic.

The most readable and type-safe solution I’ve found is to declare a templated member function

template <typename T, int N>
char ( &array_size_helper( const T (&)[N] ))[N];

#define ARRAY_SIZE( array ) (sizeof(array_size_helper( array )))
  • const T (&)[N] is a reference to a const array of type T with size N

  • char (&…​)[N] is the signature of a function that returns a reference to an array of char with size N.

  • char (&array_size_helper(const T (&arr)[N]))[N] is a function signature of a function that takes a const array reference of size N, and returns a reference to an array of char with size N.

Since array_size_helper is only a helper for ARRAY_SIZE, no implementation is needed. As ARRAY_SIZE uses the operator sizeof, which accepts an expression, it can be used on member variables without having a valid object.

The macro is type-safe because array_size_helper only takes an array as a parameter.

template <typename T, int N>
char ( &array_size_helper( const T (&)[N] ))[N];
#define ARRAY_SIZE( array ) (sizeof(array_size_helper( array )))

struct data{
  data();
  int array[4];
  static constexpr int v1 = ARRAY_SIZE(array); // fails to compile
  static constexpr int v2 = sizeof(array)/sizeof(array[0]); // compiles
};
static_assert(sizeof(data::array)/sizeof(data::array[0]) == 4); // compiles
static_assert(ARRAY_SIZE(data::array) == 4); // fails to compile

In this context, type-safe also means that implicit conversions are disallowed too (a good thing, even if there are not many types that are convertible to arrays)

typedef int int3[3];
struct s{
  int3 array;
  constexpr operator const int3&() const noexcept{
    return array;
  }
};

void foo(){
  auto var = s();
  const int3& var2 = var;
}
static_assert(ARRAY_SIZE(s()) == 3); // fails to compile

Note that ARRAY_SIZE must use internally sizeof and not std::size, otherwise one would get the invalid use of non-static data member compiler error again. Since the return of array_size_helper is a reference to an array of char, and since sizeof(char) is always 1, there is no need to divide by 1.

Conclusion

I’ve updated Macros usages in C++; unfortunately, it seems I’ve found another use-case where a #define cannot be fully replaced with "normal" C++ code.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.