12

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.

2
  • @texasbruce, thanks, I tried that. The problem is that N in std::array<size_t, N> 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? Commented Oct 7, 2015 at 18:49
  • I misinterpreted your question. enable_if won't help here Commented Oct 7, 2015 at 18:52

3 Answers 3

8

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.

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

Comments

4

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

4 Comments

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.
WrapT can have automatic conversion operator T&() and operator T const&() const. Also I can define template<class T, size_t N> using initialized_array = std::array<WrapT<T>, N>; and use it as ... function(initialized_array<size_t, N> arr){...}.
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<size_t> elements are each initialized from a single initializer 1, 2 and so on. This works because WrapT(T e) allows implicit conversions.
@dyp Thanks for that; updated. For some reason GCC complained and wanted the full syntax.
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).

2 Comments

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.
I accepted this answer because it is formally correct, the other answers are good workarounds.

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.