11

I am using std::array<size_t, N> (N is a fixed template-variable).

#include<array>
template<size_t N>
struct A{
   size_t function(std::array<size_t, N> arr){ return arr[N-1];} // just an example
};

int main(){
   A<5> a;
   a.function({{1,2,3,4,5}}));
}

And it works fine. The problem is that this other code is silently allowed:

   A.function({{1,2,3}}));

That is, even with missed elements the array is initialized somehow, even if it is well defined (e.g. remaining elements initialized to zero, I am not sure) this is a possible source of errors.

Is there a way to enforce the initialization of the extra elements? E.g. by generating a compiler error or a warning.

One option that I contemplated is to use initializer_list

   size_t function2(std::initializer_list<size_t> il){ assert(il.size() == N); ...}

The problem is that this generated a runtime error at best and a check in every call. I would prefer a compiler error/warning.

I am not so much bothered by the default initialization of std::array<>{} but by the incomplete initialization. (Maybe there is nothing it can be done about it, since this is inherited from the behavior of T[N] static array.)

I tried using clang 3.5 and gcc 5.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • @texasbruce, thanks, I tried that. The problem is that `N` in `std::array` cannot be inferred from the argument (at least in my compiler), and from there on `std::enable_if` doesn't help. Or you mean something different? – alfC Oct 07 '15 at 18:49
  • I misinterpreted your question. enable_if won't help here – SwiftMango Oct 07 '15 at 18:52

3 Answers3

7

You can enforce this by using parameter pack, but with a bit different syntax:

#include <array>

template<size_t N>
struct A{
   template<typename... T>
   size_t function(T&&... nums)
   {
     static_assert(sizeof...(nums) == N, "Wrong number of arguments");
     std::array<size_t, N> arr = { std::forward<size_t>(nums)... };
     return arr[N-1];
   }
};

int main(){
   A<5> a;
   a.function(1,2,3,4,5); // OK
   a.function(1,2,4,5);   // Compile-time error
}

But, I think there is no good way to enforce that in compile time. I would just use assert(il.size() == N) in production code to check size of initializer list.

Stas
  • 11,571
  • 9
  • 40
  • 58
3

You can create a wrapper for your objects

template <class T>
struct WrapT
{
    WrapT() = delete;

    WrapT(T e)   : value(e){}

   public: T value; 
   // or add some operator()
};

and

size_t function(std::array<WrapT<size_t>, N> arr){ return arr[N-1].value;}

so a function call like (with full brace-init)

function({ {{1}, {2}, {3}, {4}} }); 

will not compile because of use of deleted function. Live example. The syntax is a little clumsy, and even then I'm not sure all the possible value-initialization cases are covered.

@dpy points out you may omit the innermost ones, so you get to your original code: function( {{ 1, 2, 3, 4, 5 }} ).

edmz
  • 8,220
  • 2
  • 26
  • 45
  • This made me understand that the problem is not only of `std::array` but also of the element type. This answer very conceptual because it tells me that I maybe should use an explicitly initalized type. In `clang` I don't need the extra bracket around each element for some reason. – alfC Oct 07 '15 at 20:18
  • WrapT can have automatic conversion `operator T&()` and `operator T const&() const`. Also I can define `template using initialized_array = std::array, N>;` and use it as `... function(initialized_array arr){...}`. – alfC Oct 07 '15 at 20:19
  • 2
    You can use brace-elision, this restores the OP's `{{ 1, 2, 3, 4, 5 }}` syntax. The first `{` initializes the `std::array`, the second one initializes `std::array`'s internal C-style array (X), then we have a `1` which is not a `{` so brace-elision applies. The members of (X), i.e. the `WrapT` elements are each initialized from a single initializer `1`, `2` and so on. This works because `WrapT(T e)` allows implicit conversions. – dyp Oct 07 '15 at 22:06
  • @dyp Thanks for that; updated. For some reason GCC complained and wanted the full syntax. – edmz Oct 09 '15 at 09:32
2

Simple answer: You cannot.

When initializing std::array with a list, it is doing an aggregate initialization, and it is explained here when the list size is less than the number of member:

  • If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are initialized by their brace-or-equal initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, which performs value-initialization. If a member of a reference type is one of these remaining members, the program is ill-formed (references cannot be value-initialized)

It is simply a legal and acceptable behavior to provide less than the size list, so compiler will not complain anything. Your code:

A<5> a;
a.function({{1,2,3}}));

is equivalent to:

A<5> a;
a.function({{1,2,3,0,0}}));

to compiler. Your best bet is runtime error (which may not be as you wished).

SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • I guess that a runtime check is also not possible because how can one distinguish a real zero from an omitted zero for example. Changing to initializer list is a different work around. – alfC Oct 10 '15 at 22:33
  • I accepted this answer because it is formally correct, the other answers are good workarounds. – alfC Oct 10 '15 at 22:34