9

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?

2
  • Why should it compile? typename std::enable_if<std::is_array<T>::value>::type will be false, because int will faill for is_array. Commented Jan 18, 2014 at 17:37
  • @remyabel: Doh! Of course you're right. Commented Jan 18, 2014 at 17:47

5 Answers 5

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

Sign up to request clarification or add additional context in comments.

6 Comments

I agree that this solution works, but I'd really like to use the deleted overload for pointers so that users get a "you're calling a deleted function" error message instead of "a template type deduction fails" error message. That was what led to my using overloading in the first place.
@KnowItAllWannabe: Perhaps then instead of a deleted function, you can actually implement the function and put a static_assert in it.
Calling size() for containers can be compile-time in case of initializer_list - only since C++14 though.
@andyprowl can you detect if size is constexpr? And do we care?
@Yakk: Not sure. One could try SFINAE on is_constexpr, but I don't know of a robust way of writing is_constexpr. Here are some attempts. And no, we don't care. I was just saying.
|
3

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;

3 Comments

'constexpr std::size_t countof(T) = delete;' is better - return countof(...) will give an extra error, otherwise
or even constexpr std::size_t countof(T*) = delete; (and so no enable_if)
@Jarod42 no, that would make the array call ambiguous.
1

You might do this:

#include <iostream>
#include <type_traits>

namespace Detail {
    template <typename T>
    struct array_size {
        // A simple false is no good
        static_assert(std::is_array<T>::value, "No Array");
    };

    template <typename T, std::size_t N>
    struct array_size<T[N]> {
        static constexpr std::size_t value = N;
    };
}

template <typename T>
constexpr std::size_t array_size() {
    return Detail::array_size<T>::value;
}

template <typename T>
constexpr std::size_t array_size(const T&) {
    return Detail::array_size<T>::value;
}

int main(){
    typedef int A[3];
    typedef char B[array_size<A>()];
    A a;
    std::cout << array_size<A>() << array_size(a) << array_size<B>() << std::endl;
    // int* p = a;
    // error: static assertion failed: No Array
    // std::cout << array_size(p) << std::endl;
    return 0;
}

2 Comments

What are the advantages of this approach compared to yuri kilochek's approach (currently above)? If you want to use a static_assert for a custom error message, you can just put that in place of the =delete in his solution.
@KnowItAllWannabe It works with types, too. That could be accomplished in different ways, though.
1

If you need to flatten all dimensions, this excerpt can be in hand

//Moving to detail like 'Dieter Lücking'
namespace detail {

    /*recurse over ranks*/
    template <typename A, size_t R = std::rank<A>::value>
    struct aux {
        static constexpr size_t value =
        std::extent<A, 0>::value * aux<typename std::remove_extent<A>::type>::value;
    };

    /*stop condition*/
    template <typename A>
    struct aux<A, 0> {
        static constexpr size_t value = 1;
    };
}

/*convenient function, updated to use enable_if, is_array*/
template <typename A, typename = typename std::enable_if<std::is_array<A>::value>::type>
constexpr size_t countof(A const &) {
    return detail::aux<A>::value;
}

Use example:

int a[][3][3] = {
    {{1,2,3},
     {1,2,3},
     {1,2,3}},
    {{1,2,3},
     {1,2,3},
     {1,2,3}}
};
int b[countof(a)]; //size 2*3*3*1 = 18

Comments

1

For those of us who have to use legacy C++ compilers without C++11's constexpr, the following will work:

#include <cstddef>

template <class T, size_t N> char (*countof(T(&)[N]))[N]; // declaration only
#define countof(x)          sizeof(*countof(x))

int main()
{
  int a[10];
  size_t asize = countof(a);             // should compile
  static_assert(countof(a) == 10,
               "countof(a) != 10!");
  int *p;
  size_t psize = countof(p);             // shouldn't compile
}

This technique ensures the compile-time evaluation of countof by embedding the ADL within the sizeof operator.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.