Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't std::array::size static?

Tags:

c++

c++11

stl

The size of std::array is known at compile time, but the size member function isn't static. Is there any reason for that? It's slightly inconvenient not to be able to calculate the size without instantiating an object. (Well, I know about std::tuple_size specialization, but it doesn't work for classes derived from std::array.)

like image 266
lizarisk Avatar asked Feb 21 '14 13:02

lizarisk


People also ask

Is std :: array fixed size?

std::array is a container that encapsulates fixed size arrays. This container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member. Unlike a C-style array, it doesn't decay to T* automatically.

Is array size static?

Static arrays have their size or length determined when the array is created and/or allocated. For this reason, they may also be referred to as fixed-length arrays or fixed arrays. Array values may be specified when the array is defined, or the array size may be defined without specifying array contents.

Is std :: array initialized?

std::array contains a built-in array, which can be initialized via an initializer list, which is what the inner set is.

Do arrays have size () C++?

array::size() in C++ STL The introduction of array class from C++11 has offered a better alternative for C-style arrays. size() function is used to return the size of the list container or the number of elements in the list container.


2 Answers

There is no good reason for that. In fact, boost::array<T, N>, the precursor of std::array<T,N>, actually defines static size_t size(){return N;} (although a modern more useful version should use constexpr also).

I agree with the OP that this is an unfortunate omission and underexplotaition of the language features.

Problem

I faced this problem before and the logic leads to a couple of solutions. The OP situation is the following: you have a class that derives from std::array and you need to access to the size at compile time.

#include<array>  template<class T...> struct myarray : std::array< something that depends on T... >{     ... very cool functions... }; 

and later you have

template<class Array, size_t N = ???> functionOnArrayConcept(Array const& a){...} 

Where you need to know N at compile time.

As it is now, there is no code ??? that you can write that works both for std::array and myarray, because std::tuple_size<myarray<...>> will not work.

Solution

(this was suggested by @T.C. here Access maximum template depth at compile? . I am just copying it here.)

template<class T, std::size_t N> auto array_size_impl(const std::array<T, N>&)      -> std::integral_constant<std::size_t, N>;  template<class Array> using array_size = decltype(array_size_impl(std::declval<const Array&>()));  template<class Array> constexpr auto static_size() -> decltype(array_size<Array>::value){     return array_size<Array>::value; } template<class Array> constexpr auto static_size(Array const&) -> decltype(static_size<Array>()){     return static_size<Array>(); } 

Now you can use it as this:

template<class Array, size_t N = static_size<Array>()> functionOnArrayConcept(Array const& a){...} 

If you are using std::tuple_size already, unfortunately (I think) you need to specialize std::tuple_size for each of your derived classes:

namespace std{     template<class... T> // can be more complicated if myarray is not parametrized by classes only     struct tuple_size<myclass<T...>> : integral_constant<size_t, static_size<myclas<T...>>()>{}; } 

(In my opinion this is caused by another mistake in the STL design that std::tuple_size<A> doesn't have the default template<class A> struct tuple_size : A::size(){}.)


The solutions beyond this point are near obsolete compared to @T.C. solution described above. I'll keep them here for reference only.

Solution 1 (idiomatic)

If the function is decoupled from you class you have to use std::tuple_size because that is the only standard way of accessing the size of std::array at compile time. Therefore you have to do this, 1) provide a specialization of std::tuple_size and if you can control myclass, 2) std::array doesn't have static size() but your derived class could (that simplifies the solution).

So, this can be a pretty general solution within the framework of STD, that consists in the specialization of std::tuple_size. (Unfortunately providing specialization in std:: sometimes is the only way to make real generic code. See http://en.cppreference.com/w/cpp/language/extending_std)

template<class... T> struct myarray : std::array<...something that depends on T...>{     ... very cool functions...     static constexpr size_t size(){return std::tuple_size<std::array<...something that depends on T...>>::value;} };  namespace std{     // specialization of std::tuple_size for something else that `std::array<...>`.     template<class... T> // can be more complicated if myarray is not parametrized by classes only     struct tuple_size<myclass<T...>> : integral_constant<size_t, myclass<T...>::size()>{}; }  // now `functionOnArrayConcept` works also for `myarray`. 

(static size_t size() can be called differently, and there may be other ways to deduce the size of the base of myarray without adding any static function to size.)

Note

In the compilers I tried the following trick doesn't work. If this worked, the whole discussion would be less important, because std::tuple_size wouldn't be so necessary.

template<class ArrayConcept, size_t N = ArrayConcept{}.size()> // error "illegal expression", `std::declval<ArrayConcept>()` doesn't work either. functionOnArrayConcept(ArrayConcept const& a){...} 

Conceptualization

Due to this shortcoming in the implementation (or specification?) of std::array by which the only way to extract the compile time size is through std::tuple_size. Then std::tuple_size is conceptually part of the necessary interface of std::array. Therefore when you inherit from std::array you have also "inherit" std::tuple_size in some sense. And unfortunately you need to do this for further derivations. This is the concept behind this answer.

Solution 2 (a GNU hack)

If you are using GNU's STD library (that includes gcc and clang), there is a hack that can be used without adding any code, and that is by using the _M_elems member which is of (member) type ::_AT_Type::_Type (a.k.a. type T[N]) of std::array<T, N>.

This function will effectively behave like a static function ::size() (except that it cannot be used for instances of an object) of std::array or any type derived from std::array.

std::extent<typename ArrayType::_AT_Type::_Type>::value 

which can be wrapped into:

template<class ArrayType> constexpr size_t array_size(){     return std::extent<typename ArrayType::_AT_Type::_Type>::value } 

This work because the member type _AT_Type::_Type is inherited. (I wonder why GNU left this implementation detail public. Another omission?)

Solution 3 (a portable hack)

Finally, a solution using template recursion one can figure out what is the dimension of the base std::array.

template<class Array, size_t N=0, bool B = std::is_base_of<std::array<typename Array::value_type, N>, Array>::value> struct size_of : size_of<Array, N + 1, std::is_base_of<std::array<typename Array::value_type, N+1>, Array>::value>{};  template<class Array, size_t N> struct size_of<Array, N, true> : std::integral_constant<size_t, N>{};  // this is a replacement for `static Array::size()`     template<class Array, size_t N = size_of<Array>::value> constexpr size_t static_size(){return N;}  // this version can be called with an object like `static Array::size()` could template<class Array, size_t N = size_of<Array>::value>   constexpr size_t static_size(Array const&){return N;} 

This is how one will get:

struct derived : std::array<double, 3>{};  static_assert( static_size<std::array<double, 3>>() == 3 ); static_assert( static_size<derived>() == 3 ); constexpr derived d; static_assert( static_size(d) == 3 ); 

If this function is called with some type unrelated to std::array, it will give a recursion error. If you want a "soft" error instead, you have to add the specialization.

template<class Array> struct size_of<Array, 250, false> {};  

where 250 stands for a large number but smaller than the recursion limit. (I don't know how to get this number automatically, I only know the the recursion limit in my compiler is 256.)

like image 184
alfC Avatar answered Sep 30 '22 17:09

alfC


Since C++11 you can use std::tuple_size on std::array to obtain the size as a compile time constant. See

http://en.cppreference.com/w/cpp/container/array/tuple_size

like image 44
Luke Peterson Avatar answered Sep 30 '22 16:09

Luke Peterson