Query the number of elements in an array
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 aconst
array of typeT
with sizeN
-
char (&…)[N]
is the signature of a function that returns a reference to an array ofchar
with sizeN
. -
char (&array_size_helper(const T (&arr)[N]))[N]
is a function signature of a function that takes aconst
array reference of sizeN
, and returns a reference to an array ofchar
with sizeN
.
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.