6

For example

#include <array> class Range { public: Range(std::array<float, 2> ends) : m_ends(ends) {} private: std::array<float, 2> m_ends; }; 

and I can

Range r({1, 2}); 

Now I have another class

class Box { public: Box(std::array<Range, 3> ranges) : m_ranges(ranges) {} private: std::array<Range, 3> m_ranges; }; 

And I hope I can do the following

Box b({{1,2}, {3,4}, {5,6}}); 

But I cannot. How can I change the code to make it possible.

1
  • 2
    This works: Box b{{{{{1, 2}}, {{3,4}}, {{5,6}}}}}. Commented Jun 10, 2016 at 12:51

6 Answers 6

4

std::array is a bit strange. It doesn't have a user-defined constructor, so it is a lot like a plain struct. So std::array<float,2> is much like

struct two_floats { float array[2]; }; 

Because of this, if you initialize one, you would logically do it like this:

two_floats x = {{1,2}}; std::array<float,2> y = {{1,2}}; 

The outer braces are for the struct itself, and the inner braces are for the contents of the struct.

It happens to work to only provide one set of braces:

two_floats x = {1,2}; 

But this is due to a special rule in C++ that allows braces to be omitted in certain cases. Similar to how you can initialize a two-dimensional array with only one set of braces:

float x[2][2] = {1,2,3,4}; 

And this is what is happening when you initialize your range like this:

Range r({1, 2}); 

Which is equivalent to

std::array<float,2> arg = {1,2}; // one set of braces omittted Range r(arg); 

But which would more explicitly be written as:

std::array<float,2> arg = {{1,2}}; Range r(arg); 

A similar thing happens when initializing the Box. If we explicitly write out the initialization it would look like this:

std::array<float,2> box_arg1 = {{1,2}}; std::array<float,2> box_arg2 = {{3,4}}; std::array<float,2> box_arg3 = {{5,6}}; std::array<Range,3> box_args = {{box_arg1,box_arg2,box_arg3}}; Box b(box_args); 

So if we substitute the initializers, we get:

Box b({{{{1,2}},{{3,4}},{{5,6}}}}); 

and that works. But it is pretty ugly. This initialization is too complex to allow the extra braces to be omitted here, which is the problem you are running into.

One way to work around this to provide additional constructors which take the individual array elements.

class Range { public: Range(float x,float y) : m_ends{x,y} { } Range(std::array<float, 2> ends) : m_ends(ends) {} private: std::array<float, 2> m_ends; }; class Box { public: Box(Range x,Range y,Range z) : m_ranges{x,y,z} {} Box(std::array<Range, 3> ranges) : m_ranges(ranges) {} private: std::array<Range, 3> m_ranges; }; 

And you can now initialize your Box like you originally wanted:

Box b({{1,2}, {3,4}, {5,6}}); 
Sign up to request clarification or add additional context in comments.

Comments

3

I would just drop the arrays and use the usual fields. You can always add an operator[] overload if you really need it. Just change the field names to whatever it is you're actually modelling.

class Range { public: Range(float x, float y) : m_x{x}, m_y{y} {} private: float m_x, m_y; }; class Box { public: Box(Range w, Range h, Range d) : m_w{w}, m_h{h}, m_d{d} {} private: Range m_w, m_h, m_d; }; 

Live Demo

1 Comment

The operator[] will need an if judgement, does it affect performance?
1

Problem

This code: Box b({{1,2}, {3,4}, {5,6}}); is trying to aggregate initialize instances of Range even though Range is not an aggregate.

Aggregate initialization is a form of list-initialization, which initializes aggregates

An aggregate is one of the following types:

array type

class type (typically, struct or union), that has

no private or protected non-static data members

no user-provided constructors , including those inherited from public bases (sinceC++17) (explicitly defaulted or deleted constructors are allowed)

(since C++11) no virtual, private, or protected (since C++17) base classes

no virtual member functions

Solution

Invoke the constructor of Range explicitcly and only aggregate-initialize the std::array:

Box b({Range({1,2}), Range({3,4}), Range({5,6})}); 

1 Comment

It is not aggregate initialize, it is trying to use list initialize
1

You could maybe use initializer lists:

class Range { public: Range(std::initializer_list<float> ends) : m_ends(ends) {} float a() { return m_ends[0]; } float b() { return m_ends[1]; } private: std::vector<float> m_ends; }; class Box { public: Box(std::initializer_list<Range> ranges) : m_ranges(ranges) {} void print() { for (auto& i : m_ranges) { std::cout << i.a() << "," << i.b() << std::endl; } } private: std::vector<Range> m_ranges; }; Range r({ 1,2 }); Box b({ {1,2},{3,4},{5,6} }); b.print(); 

gives

1,2 3,4 5,6 

Comments

0

AFAIK it cannot be done with array, you have to resort to basic types

struct Range { float m_ends[2] ; }; Range r = {1.0f, 2.0f}; struct Box { Range m_ranges[3]; }; Box b = {{{1.0f, 2.0f}, {1.0f, 2.0f}, {1.0f, 2.0f}}}; 

Comments

0

To get a correct initialize list, first list its full form:

Box b{ array<Range, 3>{{ // std::array needs aggregate-initialize, // and have to initialize a inner array // without a addition '{', C++ is unable to // know that the inner element is Range Range{ {1, 2} // short-hand for array<float>{{3, 4}} }, Range{ {3, 4} }, Range{ {5, 6} } }} } 

Remove all the types except the Box, we got:

Box b{ {{ {{1, 2}}, {{3, 4}}, {{5, 6}} }} }

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.