38

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

lizarisk
  • 7,562
  • 10
  • 46
  • 70
  • " but it doesn't work for classes derived " why it does work? – Suma Feb 21 '14 at 14:04
  • 1
    This might be well suited for the [std-discussion](https://groups.google.com/a/isocpp.org/forum/#!forum/std-discussion) newsgroup. – Potatoswatter Feb 21 '14 at 14:04
  • @Suma Selection of specialization doesn't respect inheritance. You have to add a `tuple_size` specialization for every derived class. – Potatoswatter Feb 21 '14 at 14:05
  • 5
    Deriving from `std::array` doesn't sound like a terribly good idea in the first place. – ComicSansMS Feb 21 '14 at 14:10
  • @ComicSansMS Why not? It's POD, you can't get more harmless than that. (Although, writing `tuple_size` specializations is certainly a red flag.) – Potatoswatter Feb 21 '14 at 14:11
  • 1
    @Potatoswatter As with other STL containers, `std::array` is more of [a value-typeish](http://www.parashift.com/c++-faq/val-vs-ref-semantics.html) type. The absence of a virtual destructor alone makes it hard to use sensibly in polymorphic contexts. I cannot think of a single use-case where I wouldn't be better off using composition instead of inheritance. – ComicSansMS Feb 21 '14 at 14:13
  • 4
    @ComicSansMS So don't use it in polymorphic contexts ;) . Inheritance has various odd uses in C++. – Potatoswatter Feb 21 '14 at 14:17
  • You can actually use it with `std::shared_ptr` in a polymorphic way. `std::shared_ptr` will take care of deletion through pointer. – Brandon Feb 21 '14 at 14:20
  • 2
    @Potatoswatter Can you name one that is not better solved by composition? This is not a rhetoric question, I tried constructing one, but failed. If you know a legitimate use case, I'd honestly like to hear about it. – ComicSansMS Feb 21 '14 at 14:53
  • 2
    @ComicSansMS Apparently, the use case is when you want to retain the features of the base class and add some new, i.e. an "is a" relationship. Composition is not so convenient here because it incurs unnecessary boilerplate code clutter because: you have to forward all the functions of the base class, write copy and assignment operators etc. – lizarisk Feb 21 '14 at 15:29
  • 1
    @lizarisk An is-a relationship (imho) implies a polymorphic context, so I'd not use that term for this scenario. I do see your point of wanting to create a 'special kind of array' without having to duplicate all of the interface functions. But this boils down to using inheritance purely for code-reuse, which (again, imho) is a smell. In case of `std::array` it's debatable whether the savings in boilerplate code make up for the tight coupling introduced by the inheritance, but I see your point. Thanks for taking the time to explain. – ComicSansMS Feb 21 '14 at 15:56
  • 2
    @ComicSansMS What lizarkisk said, and also without inheritance you have to rely on perfect forwarding to bring the wrapped interface out to the wrapping class. That didn't exist before C++11, and "perfect" is actually a misnomer. Also there's the empty base optimization. Using non-public inheritance for these things eliminates the risk of the user abusing polymorphism. – Potatoswatter Feb 22 '14 at 00:54
  • More importantly, why isn't it `const`? – AnT stands with Russia Mar 14 '16 at 15:56
  • @AnT Bug. Fixed in C++14. Was a `constexpr` though. – Konrad Rudolph Mar 14 '16 at 16:21
  • @AnT In C++11 `constexpr` implies `const`, so it actually was `const`. – nwp Mar 16 '16 at 12:19
  • @nwp: Hm... Yes, you are right, it impli*ed* `const` in C++11 (but not in C++14). – AnT stands with Russia Mar 16 '16 at 14:40

7 Answers7

15

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

Dev Null
  • 4,731
  • 1
  • 30
  • 46
Luke Peterson
  • 931
  • 1
  • 9
  • 25
15

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

alfC
  • 14,261
  • 4
  • 67
  • 118
1

It can indeed be static, however, this would break "container" interface which won't play well with other generic algorithms that do expect containers to have size() member function. There is nothing to worry about, though, as std::array::size() is a constexpr function, so there is absolutely no overhead associated with it.

UPDATE:

Mr. Jrok have pointed out that one can call static member functions with "normal" syntax. Below is an example when it won't:

#include <array>

struct array {
    static unsigned int size()
    {
        return 0;
    }
};

template <typename T>
static auto do_stuff(T& data) -> decltype(data.size())
{
    typedef decltype(data.size()) size_type;
    size_type (T::*size_calc)() const = &T::size;
    size_type n = 0;
    for (size_type i = 0, e = (data.*size_calc)(); i < e; ++i)
        ++n;
    return n;
}

int main()
{
    // Below is fine:
    std::array<int, 5> data { 1, 2, 3, 4, 5 };
    do_stuff(data);

    // This, however, won't work as "size()" is not a member function.
#if 0
    array fake;
    do_stuff(fake);
#endif
}
  • 7
    You can call static member functions with 'normal' syntax, so how would that break interface? – jrok Feb 21 '14 at 14:10
  • How does that break a container interface? – lizarisk Feb 21 '14 at 14:28
  • 15
    Good point about how some generic code could fail with a static member function, however the actual container requirements don't require containers to allow member pointers to `size()`; All that's required is that the expression `a.size()` work, and a static member function fulfills that requirement. – bames53 Feb 21 '14 at 15:33
  • `// This, however, won't work as "size()" is not a member function.` Why wouldn't it work? – cp.engr Oct 15 '15 at 23:18
  • The code example above is confusing because it actually (should) works. There are other contexts in which the OP use causes problems. (not this one). – alfC Mar 14 '16 at 15:32
  • This answer makes a good point, however, if you look carefully, the original `std::array::size()` is not `const` (!). Which kinda defeats the idea of compatibility with other containers. – AnT stands with Russia Mar 14 '16 at 15:54
  • The whole thing is confusing. `std::array::size` is `const` (since C++14) `http://en.cppreference.com/w/cpp/container/array/size`. The point is that it should be `static constexpr size_t size()` instead. – alfC Mar 14 '16 at 16:34
0

array::size is constexpr, so unless the stored type has a constructor or destructor, the operation array_t().size() is very unlikely to have any runtime effect. You can embed it in a template argument to ensure it doesn't. It does otherwise look like runtime code, though.

I think that it's nonstatic simply for uniformity with other containers. For example, you can form a pointer-to-member-function to it. Discovering the true rationale of anything often takes tough research, though. It could be that the authors never thought of it.

The other thing that comes to mind is that some special functions such as operator () () cannot be static, so any opportunistic application of static can only be piecemeal. Generic problems are better solved in uniform fashion, even if it means changing the core language.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • `array_t().size()` would certainly have a runtime overhead due to initialization of a temporary array. – lizarisk Feb 21 '14 at 14:13
  • 3
    @lizarisk It's all constexpr. Although the array initialization conceptually happens, the object gets thrown away and there's never a need to actually store it anywhere, as long as it's POD. For non-POD stored types, yes, you would see the constructor and/or destructor run. – Potatoswatter Feb 21 '14 at 14:15
  • @Potatoswatter: Can it really be optimized if the value_type doesn't have a constexpr constructor? – eerorika Feb 21 '14 at 14:18
  • @user2079303: From the first line of this answer: `so unless the stored type has a constructor or destructor` – Lightness Races in Orbit Feb 21 '14 at 14:22
  • 1
    @Potatoswatter, I've checked the assembly, and it does have a runtime effect at least with -O0. – lizarisk Feb 25 '14 at 12:05
  • @lizarisk That doesn't really count. Constexpr functions (outside contexts like template arguments and array bounds) are in general not guaranteed to be evaluated at compile time, so that they can be artificially run at runtime for the sake of debugging. – Potatoswatter Feb 26 '14 at 07:34
  • More importantly array_t().size() doesn't work as template parameter (when I tried at least.) See the note in my answer. – alfC Mar 14 '16 at 13:24
  • "It could be that the authors never thought of it." . If the authors based their design on `boost::array` they must have thought about it, because in `boost::array` `size()` is `static`. – alfC Mar 16 '16 at 21:09
0

You can re-declare a same-typed empty std::array (which should get optimized out) and take the size of that. For example:

// Pretend this is an expensive initialization; e.g., a function return value.
std::array<char, 0x123> some_array{1,2,3};
using type_of_some_array = decltype(some_array);

// Find the size without accessing the array.
auto constexpr size_of_some_array = type_of_some_array().size();

std::cout << size_of_some_array << std::endl;

compiles to:

00000000004006f0 <main>:
  4006f0:       48 83 ec 08             sub    $0x8,%rsp
  4006f4:       be 23 01 00 00          mov    $0x123,%esi
  4006f9:       bf 60 10 60 00          mov    $0x601060,%edi
  4006fe:       e8 ad ff ff ff          callq  4006b0 <_ZNSo9_M_insertImEERSoT_@plt>
  ...

[No issues found when using size_of_some_array as a template parameter: which was counter-alluded to in the comments on @Potatoswatter's answer.]

Daniel
  • 137
  • 1
  • 4
-1

Note that the Microsoft Visual C++ doesn't currently support constexpr (http://msdn.microsoft.com/en-us/library/hh567368.aspx), so the following valid code won't work:

    array<int,3> dog;
    array<double, dog.size( )> cat;

The following class provides a compile time static variable:

/**
* hack around MSVC's 2012 lack of size for const expr
*/
template <typename T, int N>
struct vcarray : public std::array<T,N> {
    static const size_t ArraySize= N;
};

which can be used as:

vcarray<double,3> cat;
vcarray<double,cat.ArraySize> dog;
gerardw
  • 5,822
  • 46
  • 39
-5

In my opinion it does not make sense to make the size member function static insofar as it provides no added value. It is possible to make it static, but you gain nothing from it.

The way the array class is designed, you can query the size of a given array object without explicitly knowing/remembering its exact type (which includes its size) at that location where you need the size. This is a convenience, and it removes the opportunity to make copy/edit errors. You can write code like this:

std::array<int, 5> blah;
// 50 lines of code
do_something_with(blah.size()); // blah knows its size

As you can see, at the location where I'm consuming the array's size, I don't actually remember what it was, but my code will work anyway, regardless of what the value actually is, and regardless whether maybe one day I change the array's type to be a different size.
Since the size function merely returns a template parameter, the compiler can trivially prove that the return value is a compile-time constant and optimize accordingly too (the function is also constexpr, so you can also use the return value as template parameter or enumeration).

Now what will be different if we make the size member function static?

If size was a static function, you could still use the static member function in the exact same way (that is, on an object instance, in a "not static way"), but that would be "cheating". After all, this is something that already works anyway, whether the member is static or not.
Further, you now have the possibility of invoking the member function without an object instance. While this seems like a good thing at first glance it really is no advantage at all for the array class template (...where the returned size is a template parameter).

In order to call a member function without an object (that is, in a "static member function way"), you must properly qualify the function with the class name and its proper template parameters.
In other words, you must write something like:

std::array<int, 5> blah;
// 50 lines of code
do_something_with(std::array<int,5>::size()); // I must tell size what to return

Now what have we gained from calling the size function? Nothing at all. In order to call the function, we needed to provide the correct template parameters, which includes the size.

That means no more and no less than that we must supply the information that we wish to query. Calling the function doesn't tell us anything we didn't already know.

Damon
  • 67,688
  • 20
  • 135
  • 185
  • Static members can be called with normal syntax on the instance, you don't have to provide the type. – lizarisk Feb 21 '14 at 14:23
  • 3
    The benefit is the ability to use it without an object instance, apparently. `virtual` is irrelevant here as it changes a lot of things and adds overhead. `static` doesn't really change anything significant as I can see. – lizarisk Feb 21 '14 at 15:22
  • (reworded answer, deleted comments) – Damon Feb 25 '14 at 15:51
  • 1
    The benefit would be what @lizarisk said, but more importantly that you can evaluate `Array::size()` at compile time. Many times a function acting on `std::array` (or derived classes, or conceptually similar classes) is written as `template fun(Array const& a){...}`. If you need to know its size at compile time then you have a problem. – alfC Mar 14 '16 at 15:12
  • @alfC: Well yes, but you _must already know_ the size so you can evaluate `size()`. Which makes it kind of pointless (well not really pointless, but syntactic sugar that makes code easier). The array's size is a template parameter, it must be known at compile time before you even think about invoking any of its members. It arguably makes writing a template function that accepts _any type_ having a `size` member and that needs a size for some reason easier, but no new knowledge is really gained, it's only things you already know. – Damon Mar 14 '16 at 15:32
  • I am not saying that the (compile constant) `size` can not be deduced somehow. I am saying that it cannot be deduced generically for all classes that fulfill the concept of an array. Supposed I tell you have a class `myclass` inherits from some `std::array` where N can be anything. How do you know at compile-time what is the size of the base class of `myclass`? (See my answer for details.) – alfC Mar 14 '16 at 15:41