Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How compute array size during compilation (without accepting pointers)?

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?

like image 600
KnowItAllWannabe Avatar asked Jan 18 '14 17:01

KnowItAllWannabe


People also ask

How do you find the size of an array?

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]);

How do I get the size of a pointer in C++?

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).

Will return the size of the pointer not the array itself?

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.


2 Answers

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 countofable. If you pass a pointer, you are told that the function you call is deleteed.

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 image 154
Yakk - Adam Nevraumont Avatar answered Oct 11 '22 05:10

Yakk - Adam Nevraumont


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;
like image 34
yuri kilochek Avatar answered Oct 11 '22 05:10

yuri kilochek