Initializer-list on 3 levels - c++

How do I properly and easily initialize an instance of a class that contains a std::vector of some other class that in itself contains some data.
I understand that it is really hard to explain it in words, so I will instead write a piece of code that does not work, but it captures my intention.
#include <vector>
struct Point
{
float x, y;
};
struct Triangle
{
Point points[3];
};
struct Geometry
{
std::vector<Triangle> triangles;
};
int main()
{
Geometry instance
{
{{0,0}, {6, 0}, {3, 3}},
{{5,2}, {6, 6}, {7, 3}}
};
return 0;
}
This code does not work. Clang returns an error -
excess elements in struct initializer
I can not figure out why it is giving me this error.
I imagine that I can initialize
the std::vector of Triangles,
then the array of Points,
then the two floats within each Point object.
How would I go about properly initializing an instance of Geometry class with some values without writing too much code using the initializer brackets?
If you have alternatives, then I am open to considering them.

You need 2 pairs of additional braces for this to work:
Geometry instance
{{
{{{0,0}, {6, 0}, {3, 3}}},
{{{5,2}, {6, 6}, {7, 3}}}
}};
Here's a demo.
Explanations for all the braces:
Geometry instance
{ // for the Geometry object - instance
{ // for the vector member - triangles
{ // for the individual Triangle objects
{ // for the Point array - points[3]
{0,0}, {6, 0}, {3, 3}}}, // for the individual Points
{{{5,2}, {6, 6}, {7, 3}}}
}};
While I like using brace-init lists, when you have nesting this deep it might be more readable to spell out the types explicitly.

You can mention the triangles's type and provide a set of extra parentheses, then it should work
Geometry instance{
std::vector<Triangle> // explicitly mentioning the type
{
{ { {0,0}, {6, 0}, {3, 3}} },
{ { {0,0}, {6, 0}, {3, 3}} }
}
};

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.

Advantages of passing std::intializer_list and to have overloads with it

Code example to have some context:
#include <vector>
class DatasetValue
{
public:
using ValueType = int;
using Values = std::vector<ValueType>;
DatasetValue(Values values)
: m_values{std::move(values)}
{
}
const Values &values() const
{
return m_values;
}
private:
Values m_values;
};
int main()
{
using Dataset = std::vector<DatasetValue>;
auto dataset = Dataset{
{{1, 2, 3, 4, 5}},
{{7, 6, 5, 4, 3, 2, 1}},
{{1, 2, 3, 4, 5, 6, 7, 8, 9}},
{{3, 2, 1}},
{{1}},
{{ 4, 3, 2, 1}}
};
}
So, we have some Dataset object that usually will be initialized with some predefined values on application startup and it works good.
However, then I've changed constructor parameter type to std::initilizer_list and noticed that size of generated binary is reduced by ~1 KB (in my environment, ARM Cortext M4 target). Considering that DatasetValue won't be initiliezed by lvalues or rvalues of containter_type now its constructor looks like:
DatasetValue(std::initializer_list<ValueType> values)
: m_values{values} {}
We also can see something similiar on compiler explorer
So, my questions is:
Is it a good idea trying to provide constructor overloads that take std::initializer_list
with/instead of (depending on context) lvalue or rvalue of container_type ones?
Notes:
Haven't tried it yet, but I'm interested in if this also give us some benefits with other containters (list, map, etc.). Will be happy to know your thoughts)
Update: Tests were done with no optiomization enabled (see comments)

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
}

C++ sort multidimensional array

I am new in C++
I have simple quesstion
my array:
reporter[3][2] = { {0, 7}, {1, 12}, {2, 3} };
I want to sort like this:
{ {1, 12}, {0, 7}, {2, 3} };
I want to sort by second sector.
thanks a lot
This can be done is some steps:
Take all 2nd elements to a 1D array of structures. Let it is node temp[];
where node is like this:
struct node{
int value;
int position;
};
Sort them using any technique on basis of temp[i].value.
Then copy the array to a resultant array according to the temp[i].position from the sorted array.

Why is struct array init not working when using "new" in C++?

I'm learning C++ and I've encountered an interesting behavior.
Let's say I've got a struct Point with int x and int y.
I want to create an array of Points using "tagged structure initialization" syntax. This method works, the result is 3:
Point p[3] { {1, 2}, {3, 4}, {5, 6} };
cout << p[1].x;
But if I want to create these with the new keyword, it does not work. I don't get any compiler errors, nor runtime errors, but the fields remain uninitialized:
Point *p = new Point[3] { {1, 2}, {3, 4}, {5, 6} };
cout << p[1].x; // some random number from memory
Regular constructors work this way, but this doesn't. I understand that this is NOT a constructor, just a simplified syntax to assign values to the fields at once, but I'm curious why the first solution works, and the second one not.
Thank You for helping me!