15

I would like to be explicit about array size restrictions on a member variable, to stop others from accidentally making silly changes. The following naive attempt will not compile:

struct Foo
{
    std::array< int, 1024 > some_array;
    static_assert( (some_array.size() % 256) == 0, "Size must be multiple of 256" );
    //^ (clang) error: invalid use of non-static data member 'some_array'
};

Even though std::array::size is a constexpr, I can't directly use static_assert like that because neither the function nor my member variable is static.

The solution I came up with is to use decltype (since I don't want to typedef the array) as follows:

static_assert( (decltype(some_array)().size() % 256) == 0, "Size must be multiple of 256" );

This looks like it's default-constructing an rvalue, which I didn't think is a constexpr.

Why does this work?

Is there a cleaner way to achieve the static assertion?

paddy
  • 60,864
  • 6
  • 61
  • 103

2 Answers2

16

because neither the function nor my member variable is static.

Right. The problem is that the static assert can't refer to a non-static member, because some_array.size() is equivalent to this->some_array.size() and there is no this pointer at class scope (only inside function declarators and default member initializers).

However, it is OK to say decltype(array_size) because that isn't actually trying to refer to the object array_size or invoke its member functions, it's just querying the type of a name declared in the class.

This looks like it's default-constructing an rvalue, which I didn't think is a constexpr.

array<int, N> is a literal type, so can be constructed in constant expressions. The fact you're constructing an rvalue doesn't matter, you can construct a literal type and call a constexpr member function on it in a constant expression.

Something like array<std::string, N> could not be used there, because std::string is not a literal type, and so neither is array<string, N>.

Is there a cleaner way to achieve the static assertion?

The standard trait std::tuple_size is specialized for std::array so you can do:

static_assert( std::tuple_size<decltype(some_array)>::value % 256) == 0,
               "Size must be multiple of 256" );
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Unfortunately, this does not solve the problem when you inherit from `std::array` and want to use structure bindings for the derived class. In this case, you need to define a partial template specialization of and can thus not rely on `std::tuple_size` for your derived class. – Matthias Jun 01 '18 at 16:46
9

std::array::size is constexpr and should work with static_assert, however it doesn't work in this particular context (i.e., in class definition) because some_array is a non-static member variable.

For this particular context you could use a home made type trait to take the size at compile time like below:

template<typename>
struct array_size;

template<typename T, std::size_t N>
struct array_size<std::array<T, N>> {
  static const std::size_t size = N;
};

And static_assert as:

static_assert(array_size<decltype(some_array)>::size % 256) == 0, "Size must be multiple of 256" );

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168