Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement convenient initialization?

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.

like image 768
Tian Xiao Avatar asked Jun 10 '16 11:06

Tian Xiao


2 Answers

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}});
like image 120
Vaughn Cato Avatar answered Oct 09 '22 01:10

Vaughn Cato


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

like image 37
TartanLlama Avatar answered Oct 09 '22 03:10

TartanLlama