Given an array a
, I want countof(a)
to yield the number of elements in the array as a compile-time constant. If I have a pointer p
, I want countof(p)
to not compile. This seems like it should be (1) straightforward and (2) commonly covered in SO, but (1) I can't get it to work, and (2) searching SO didn't turn up anything.
Here's my attempt.
#include <cstddef>
#include <type_traits>
template<typename T, std::size_t n,
typename = typename std::enable_if<std::is_array<T>::value>::type>
constexpr std::size_t countof(T (&)[n]) { return n; }
template<typename T,
typename = typename std::enable_if<std::is_pointer<T>::value>::type>
void countof(T*) = delete;
int main()
{
int a[10];
auto asize = countof(a); // should compile
static_assert(countof(a) == 10,
"countof(a) != 10!");
int *p;
auto psize = countof(p); // shouldn't compile
}
Help?
We can find the size of an array using the sizeof() operator as shown: // Finds size of arr[] and stores in 'size' int size = sizeof(arr)/sizeof(arr[0]);
If we have some object type T, and we have a pointer T* ptr which points to an object of type T, sizeof(ptr) would give us the size of the pointer and sizeof(*ptr) would give us the size of the object ie. sizeof(T).
The sizeof() operator returns pointer size instead of array size. The 'sizeof' operator returns size of a pointer, not of an array, when the array was passed by value to a function. In this code, the A object is an array and the sizeof(A) expression will return value 100.
template<typename T, std::size_t N>
constexpr std::size_t countof( T const(&)[N] ) { return N; }
passes both of your tests. There is no way to convert an int*
into a T const(&)[N]
, so no disabling code is needed.
To extend it we should add:
template<typename T, std::size_t N>
constexpr std::size_t countof( std::array<T,N> const& ) { return N; }
I might even be tempted to extend it to calling size()
for containers. While it won't usually be compile-time, the uniformity might be useful:
for(int i=0; i<countof(c); ++i) {
// code
}
or what have you.
template<typename T, std::size_t N>
constexpr std::size_t countof( T const(&)[N] ) { return N; }
template<typename T> struct type_sink { typedef void type; };
template<typename T> using TypeSink = typename type_sink<T>::type;
template<typename T, typename=void>
struct has_size : std::false_type {};
template<typename T>
struct has_size<T, TypeSink< decltype( std::declval<T>().size() ) > >:
std::true_type
{};
template<bool b, typename T=void>
using EnableIf = typename std::enable_if<b,T>::type;
template<typename T>
constexpr
EnableIf<has_size<T const&>::value,std::size_t>
countof( T const& t ) {
return t.size();
}
// This is optional. It returns `void`, because there
// is no need to pretend it returns `std::size_t`:
template<typename T>
constexpr
EnableIf<std::is_pointer<T>::value>
countof( T const& t ) = delete;
which is pretty verbose, but gives us std::array
support, std::initializer_list
support, C-style array support -- all at compile time -- and at run time standard containers and strings are all countof
able. If you pass a pointer, you are told that the function you call is delete
ed.
I attempted to create a static_assert
in that case, but ran into problems with the resolution rule that any template
must have a valid specialization. I suspect routing the entire problem into a countof_impl
class with SFINAE based specializations might fix that problem.
A downside to the =delete
or static_assert
solution is that an overload actually exists for pointers. If you don't have that, then there simply is no valid function to call that takes a pointer: this is closer to the truth.
Like this:
template <typename T, std::size_t n>
constexpr std::size_t countof(T (&)[n]) { return n; }
template <typename T, typename = typename std::enable_if<std::is_pointer<T>::value>::type>
constexpr std::size_t countof(T) = delete;
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