How to Create Std::Array with Initialization List Without Providing Size Directly

Deduce std::array size?

C++17 std::array class template argument deduction (CTAD)

Starting with C++17, this new language feature is now used by the standard library and now allows us to omit the template types as well so that the following works:

main.cpp

#include <array>

int main() {
std::array a{1, 2, 3};
}

instead of std::array<int, 3> a{1, 2, 3};

Tested with:

g++ -ggdb3 -O0 -std=c++17 -Wall -Wextra -pedantic -o main.out main.cpp

If we set -std=c++14 instead for example, it fails to compile with:

error: missing template arguments before ‘a’

Tested on Ubuntu 18.04, GCC 7.5.0.

How come std::initializer_list is allowed to not specify size AND be stack allocated at the same time?

The thing is, std::initializer_list does not hold the objects inside itself. When you instantiate it, compiler injects some additional code to create a temporary array on the stack and stores pointers to that array inside the initializer_list. For what its worth, an initializer_list is nothing but a struct with two pointers (or a pointer and a size):

template <class T>
class initializer_list {
private:
T* begin_;
T* end_;
public:
size_t size() const { return end_ - begin_; }
T const* begin() const { return begin_; }
T const* end() const { return end_; }

// ...
};

When you do:

foo({2, 3, 4, 5, 6});

Conceptually, here is what is happening:

int __tmp_arr[5] {2, 3, 4, 5, 6};
foo(std::initializer_list{arr, arr + 5});

One minor difference being, the life-time of the array does not exceed that of the initializer_list.

How to initialize all members of an array to the same value?

Unless that value is 0 (in which case you can omit some part of the initializer
and the corresponding elements will be initialized to 0), there's no easy way.

Don't overlook the obvious solution, though:

int myArray[10] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };

Elements with missing values will be initialized to 0:

int myArray[10] = { 1, 2 }; // initialize to 1,2,0,0,0...

So this will initialize all elements to 0:

int myArray[10] = { 0 }; // all elements 0

In C++, an empty initialization list will also initialize every element to 0.
This is not allowed with C until C23:

int myArray[10] = {}; // all elements 0 in C++ and C23

Remember that objects with static storage duration will initialize to 0 if no
initializer is specified:

static int myArray[10]; // all elements 0

And that "0" doesn't necessarily mean "all-bits-zero", so using the above is
better and more portable than memset(). (Floating point values will be
initialized to +0, pointers to null value, etc.)

C++11: Correct std::array initialization?

This is the bare implementation of std::array:

template<typename T, std::size_t N>
struct array {
T __array_impl[N];
};

It's an aggregate struct whose only data member is a traditional array, such that the inner {} is used to initialize the inner array.

Brace elision is allowed in certain cases with aggregate initialization (but usually not recommended) and so only one brace can be used in this case. See here: C++ vector of arrays

How to initialize std::array member in constructor initialization list when the size of the array is a template parameter

std::index_sequence was provided in order to simplify the metaprogramming task of creating and expanding a pack whose size is not fixed. Use std::make_index_sequence and delegate the construction to some private constructor that deduces the pack:

A(H h) : A(h, std::make_index_sequence<N>{}) {}

template <std::size_t... i>
A(H h, std::index_sequence<i...>) : hashes{((void)i, h)...} {}

Here, ((void)i, h) is a comma expression that evaluates to h, but since we mentioned the pack i inside it, it's eligible to be expanded using .... The result is N copies of h (one for each element of i). This expansion occurs inside a braced-init-list, so the result is a braced-init-list contaning N copies of h, which will then initialize the hashes member.

How to initialize a class like std::array

std::array is an aggregate and so it uses aggregate initialization. Providing excess elements during aggregate initialization is ill-formed and requires a diagnostic. The compiler at minimum has to provide a warning, both gcc and clang make this an error. So if you make your class an aggregate then you can have it work the same way std::array does. Note, in class member initializers makes your class a non-aggregate in C++11 but not in C++14.

We can see it is an aggregate by going to the draft C++11 standard section 23.3.2.1 [array.overview]:

An array is an aggregate (8.5.1) that can be initialized with the
syntax

array<T, N> a = { initializer-list };

where initializer-list is a comma-separated list of up to N elements
whose types are convertible to T.

and section 8.5.1 [dcl.init.aggr] covers aggregate intialization and says:

An initializer-list is ill-formed if the number of initializer-clauses
exceeds the number of members or elements to initialize.

The draft standard provides for exposition a possible implementation which shortened to the bare minimum looks like this:

template <class T, size_t N>
struct array {
T elems[N];
};

and the draft standard has a note which says:

The member variable elems is shown for exposition only, to emphasize that array is a class aggregate.
The name elems is not part of array’s interface

which is an aggregate and will with both gcc and clang provide an error if excess elements are provided.

Also see What are Aggregates and PODs and how/why are they special?.

How can I initialize an array in C++ with a size that is given to the constructor?

std::vector is the best match here. (As you said, in most cases raw arrays and pointers could be avoided. Also see How can I efficiently select a Standard Library container in C++11?)

And you can initialize the data member directly in member initializer list instead of assigning in the constructor body after default-initialization, e.g.

Obj(int xsize, int ysize) : array(xsize, std::vector<int>(ysize, 0)) {
}


Related Topics



Leave a reply



Submit