There are several good reasons to prefer
#include <cstdlib>
template<typename T, std::size_t N>
constexpr std::size_t ARRAY_COUNT_FUNC(T (&arr)[N]) { return N; }
rather than
#define ARRAY_COUNT_MACRO(arr) (sizeof(arr)/sizeof(*arr))
One important difference is that when a pointer (not an array) is passed to ARRAY_COUNT_MACRO
, it silently returns an unhelpful answer, but passing the same argument to ARRAY_COUNT_FUNC
will cause a compiler error pointing out the mistake.
But the macro does have one advantage: its argument is unevaluated.
#include <utility>
struct S {
int member_array[5];
};
// OK:
std::size_t count1 = ARRAY_COUNT_MACRO(std::declval<S&>().member_array);
// ERROR: std::declval is odr-used!
std::size_t count2 = ARRAY_COUNT_FUNC(std::declval<S&>().member_array);
Is there another approach with the benefits of both together? I. e., something that causes a compile error if the argument is not an array, and does not odr-use its argument.
Shamelessly ripped off from the Chromium project, as described here.
#include <utility>
#include <cstdlib>
template<typename T, std::size_t N>
constexpr std::size_t ARRAY_COUNT_FUNC(T (&arr)[N]) { return N; }
#define ARRAY_COUNT_MACRO(arr) (sizeof(arr)/sizeof(*arr))
// Template for typesafey goodness.
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
// sizeof to avoid actually calling the function.
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
struct S {
int member_array[5];
};
int main()
{
// OK:
std::size_t count1 = ARRAY_COUNT_MACRO(std::declval<S&>().member_array);
// ERROR: std::declval is odr-used!
//std::size_t count2 = ARRAY_COUNT_FUNC(std::declval<S&>().member_array);
// OK:
std::size_t count2 = arraysize(std::declval<S&>().member_array);
// ERROR:
// int * p;
// std::size_t count3 = arraysize(p);
}
... And then I remembered <type_traits>
has a std::is_array
template. Another solution:
#include <type_traits>
template<typename T>
constexpr auto ArrayCountImpl(std::nullptr_t)
-> typename std::enable_if<std::is_array<typename
std::remove_reference<T>::type>::value,
std::size_t>::type
{ return std::extent<typename std::remove_reference<T>::type>::value; }
template<typename T>
std::size_t ArrayCountImpl(...)
{ static_assert(!std::is_same<T,T>::value,
"Argument type is not an array"); }
#define ARRAY_COUNT_MACRO_2(arr) (ArrayCountImpl<decltype(arr)>(nullptr))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With