List initialization for array<array<int, M>, N> - c++

When initializing a 2D array we can do
int data[2][2] = {{1, 2}, {3, 4}}; // OK
we can also use
int data[2][2] = {1, 2, 3, 4}; // OK
which makes sense since a 2D array still is a continues chunk of memory.
When using the array class instead of the base type, this works
array<array<int, 2>, 2> data = {1, 2, 3, 4}; // OK
Which again makes sense, since array class doesn't have any extra data and ends up as a continues chunk of memory as well.
But the 2D list does not work:
array<array<int, 2>, 2> data = {{1, 2}, {3, 4}}; // Error
I can also initialize this
vector<array<int, 2>> data = {{1, 2}, {3, 4}}; // OK
But couldn't find anyway to initialize:
array<vector<int>, 2> = ????
My question is:
Is there a fundamental design reason for this (My guess is something related to stuff happening at compile-time vs run-time)? Or is this just an implementation decision for the compiler I am using (GCC)?

std::array doesn't have any user-defined constructor (like std::vector), it just contains a underlying array, when performing aggregate initialization you need one more braces for it.
array<array<int, 2>, 2> data = {{{{1, 2}}, {{3, 4}}}};
// ^ ^ <- for array<array<int, 2>, 2>
// ^ ^ <- for the underlying array
// ^ ^ <- for the 1st array<int, 2>
// ^ ^ <- for its underlying array
// ^ ^ <- for the 2nd array<int, 2>
// ^ ^ <- for its underlying array
We can omit braces as
array<int, 2> data = {1, 2};
array<array<int, 2>, 2> data = {1, 2, 3, 4};
because of brace elision:
The braces around the nested initializer lists may be elided (omitted), in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, and the subsequent initializer clauses are used to initialize the following members of the object.
That means the above code code be written as
array<array<int, 2>, 2> data = {{{1, 2}, {3, 4}}};
And array<array<int, 2>, 2> data = {{1, 2}, {3, 4}}; fails because it's interpreted as:
array<array<int, 2>, 2> data = {{1, 2}, {3, 4}};
// ^ ^ <- for array<array<int, 2>, 2>
// ^ ^ <- for the underlying array
// ^ ^ <- excess elements; std::array has only one underlying array

Related

Initilizing 2D array class in c++ and passing it to a function

I've created a function to calculate a 3x3 determinant. This is its prototype:
double threeDet(array<array<double, 3>, 3> det);
I've used class array because it seems safer than traditional arrays.
I wanted to test the function, so I called it:
threeDet({ {2, -3, 1}, {2, 0, -1}, {1, 4, 5} });
However, it raises an error of "too many initializers"
This has also happened when I changed the function to:
double threeDet(double det[][3]);
Initializing the array first raises the same error:
array<array<double, 3>, 3> det({ {2, -3, 1}, {2, 0, -1}, {1, 4, 5} });
threeDet(det);
the only thing that worked is to change the function to the second version, and initialize the array separately:
double det[3][3] = { {2, -3, 1}, {2, 0, -1}, {1, 4, 5} };
threeDet(det);
I wonder what am I missing, and if there is a way to initialize that I'm missing.
There's a good rule of thumb to follow until one becomes comfortable with braced initialization lists, and how they work: when a std::array is involved, double-up the braces:
threeDet({{ {{2, -3, 1}}, {{2, 0, -1}}, {{1, 4, 5}} }});
A std::array is an aggregate object containing one class member: the array itself. So:
The outer set of braces construct the std::array.
The inner set of braces construct its class member array.
Now, once you get passed that part, you have a pair of braces that construct each value in the array. Well, each such value is also a std::array, so you need to drop in another pair of braces to initialize its array class member.

How to properly aggregate-initialize nested std::array?

I have a very simple structure that looks like this:
struct Arr
{
static constexpr auto width = 2;
static constexpr auto height = 2;
std::array<std::array<int, width>, height> values;
};
How do I properly initialize this structure without getting compiler errors or warnings?
I tried the following (see example on Compiler Explorer):
auto arr = Arr{1, 2, 3, 4}; // clang: warning: suggest braces around initialization of subobject [-Wmissing-braces]
auto arr = Arr{{1, 2}, {3, 4}}; // clang: error: excess elements in struct initializer
auto arr = Arr{{{1, 2}, {3, 4}}}; // clang: error: excess elements in struct initializer
I know there have been similar questions asked (e.g. Nested aggregate initialization of std::array), but it seems all of them focus on C++11/14.

Can I use structured bindings and a for-each loop to iterate through a few “packed-together” values?

I often use initializer lists and for-each loops to iterate through a small number of ad-hoc values, like so:
for (auto x : {1, 2, 6, 24, 120}) {
do_something(x);
}
I recently tried to write something similar, but with structured bindings and packed-together values instead:
for (auto[dx, dy] : {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}) {
try_to_move(dx, dy); // nope! won’t compile
}
Unfortunately, this doesn’t compile. Clang tells me:
error: cannot use type ‘void’ as a range
In fact, even something like auto mylist = {{1, 2}, {3, 4}}; won’t compile.
This leaves me with two questions:
Is there an alternative syntax to accomplish what I want in a terse and readable manner?
Why doesn’t the type of auto mylist get parsed as initializer_list<initializer_list<int>>? Wouldn’t that work fine?
Why doesn’t the type of auto mylist get parsed as initializer_list<initializer_list<int>>? Wouldn’t that work fine?
The reason is that simply no-one proposed it yet.
The handy syntax auto x = {1, 2, 6, 24, 120}; comes from proposal N3912 which was adopted into C++17 (see also N3922).
The deduction process is outlined in [dcl.type.auto.deduct]/4:
If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initialization is copy-list-initialization, with std​::​initializer_­list<U>. Deduce a value for U using the rules of template argument deduction from a function call, where P is a function template parameter type and the corresponding argument is e. If the deduction fails, the declaration is ill-formed.
Since type deduction in a hypothetical function call f({1, 2}) would fail, so too is the nested braced-init-list deduction auto x = { {1, 2}, {3, 4} }; also impossible.
I guess the same trick could be applied to a deduction from a function call, which would make nested initializer_list deduction possible.
So a follow-up proposal is welcome.
Is there an alternative syntax to accomplish what I want in a terse and readable manner?
You could define a good old multidimensional array:
int lst[][2] = { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
for (auto [x, y] : lst) {
. . .
}
Or as suggested in the comments, give the first pair a type to help the deduction:
for (auto [x, y] : { std::pair{1, 2}, {3, 4}, {5, 6}, {7, 8} }) {
. . .
}
You can use it like this. You just need to initialize your ad-hoc list to a variable first.
vector<pair<int, int> > p = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (auto x : p) {
try_to_move(x);
}
You then access the parameter x like this in the function
<return-type> try_to_move(pair<int, int> x){
int dx = x.first;
int dy = x.second;
// TO-DO
}

How to aggregate-initialize STL container in expression with new operator?

I'm trying to do something like:
using std::array;
array< array<int, 3>*, 10> arrsPtrs;
void f()
{
arrsPtrs[0] = new array<int, 3> {1, 2, 3}; //bad!
arrsPtrs[0] = new array<int, 3> {{1, 2, 3}}; //bad!
arrsPtrs[0] = new array<int, 3> ( {1, 2, 3} ); //bad!
arrsPtrs[0] = new array<int, 3> ( {{1, 2, 3}} ); //bad!
}
Is it possible to aggregate-initialize STL container in such expression?
PS: may be it's important - I have VS2010.
You want arrayPtrs[0] = new array<int, 3> {{1,2,3}}.
That having been said, why are you doing this? Having an array of pointers to dynamically allocated arrays is almost certainly the wrong way of going about whatever you are trying to do.
Edit: Effectively, every time you new something in C++, you need a corresponding delete. Unlike languages like Java or C# (that are garbage collected), you should rarely use a raw new in modern C++, as it can very easily lead to memory leaks.
Basically, instead of:
array<array<int, 3>*, 10> arrsPtrs;
You instead would want to use:
array<array<int, 3>, 10> arrsPtrs;
If you want to pass this to a function, you can simply pass it by (const) reference:
void some_function(const array<array<int, 3>, 10>& arr)
{
....
}

Multidimensional std::array [duplicate]

This question already has an answer here:
Why can't simple initialize (with braces) 2D std::array? [duplicate]
(1 answer)
Closed 4 years ago.
In C++, how do I create a multidimensional std::array? I've tried this:
std::array<std::array<int, 3>, 3> arr = {{5, 8, 2}, {8, 3, 1}, {5, 3, 9}};
But it doesn't work. What am I doing wrong and how do I fix this?
You need extra brackets, until c++14 proposal kicks in.
std::array<std::array<int, 3>, 3> arr = {{{5, 8, 2}, {8, 3, 1}, {5, 3, 9}}};