Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 making variadic constructor understand an initialization list of initialization lists

Let's say I have a class of Point and and an array of Points, where the number of points is given by a template parameter. How do I get initialization to work using braces? If I use:

class Point {
public:
    float x, y;
    Point(float x, float y) : x(x), y(y) {}
};

template <size_t N>
class Array {
private:
    std::array<Point, N> _points;

public:
    template <typename... Points>
    Array(const Points& ... points) : _points({ points... }) {
    }
};

Then this works:

Array<2> a{Point{1,1}, Point{2, 2}};

But If I don't provide explicit Point objects, I get an error in Xcode:

Array<2> c{{1,1}, {2, 2}};

The error is: "No matching Constructor for initialization of Array<2>". For the particular constructor it says "Candidate constructor not viable: requires 0 arguments, but 2 were provided".

How do I get this to work?

like image 545
Ant6n Avatar asked Mar 30 '16 22:03

Ant6n


People also ask

What is constructor initialization list?

Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.

Is assigned in constructor body consider performing initialization in initialization list?

Note: It is mandatory that a reference or a const member must be intialized in a constructor initialization list. They cannot be 'assigned' in the body of the constructor.

What is an initialization list in C++?

The initializer list is used to directly initialize data members of a class. An initializer list starts after the constructor name and its parameters.

Why initialization list is used in C++?

Initialization lists allow you to choose which constructor is called and what arguments that constructor receives. If you have a reference or a const field, or if one of the classes used does not have a default constructor, you must use an initialization list.


1 Answers

Why doesn't your code work

You're bracing in a braced-init-list, {{1, 1}, {2, 2}}. That thing doesn't have a type. It's just a collection of stuff. Template deduction can't really work because that could be anything - there is an infinite amount of types that could be constructed from that list. Even though you clearly want Points... to be Points, that just can't work.

What do to instead

The only way to take a braced-init-list to mean an arbitrary amount of Ts (for some type T) is to use std::initializer_list:

Array(std::initializer_list<Point> points) { ... }

But to initialize the array from the initializer_list, you'd need something like std::copy, but since Point isn't default-constructible, this is a non-starter unfortunately.

We could instead just take an array directly:

Array(std::array<Point, N> const& a)
    : _points(a)
{ }

That lets you do what you want. Or at least, with a few extra braces:

Array<2> c{{{{1, 1}, {2,2}}}};

That is an annoyingly excessive amount of braces!

So one trick I like instead is to actually create a constructor that takes N Points, which is what you wanted to begin with. We can do that by making Array a partial specialization with the index sequence trick:

template <size_t N, class = std::make_index_sequence<N>>
class Array;

template <size_t N, size_t... Is>
class Array<N, std::index_sequence<Is...>> {
private:
    std::array<Point, N> _points;

    template <size_t>
    using Point_ = Point;

public:
    Array(Point_<Is>... points)
        : _points{{points...}}
    { }
};

Here, Point_<I> is just an alias template for Point, we use it just to unpack the index sequence into a bunch of Points. That constructor is thus a non-template that takes precisely N Points and we can then use it with a sane amount of braces:

Array<2> c{{1, 1}, {2,2}}; // ok!

Well, not really a non-starter. There's several things you can do to still get this to work. You could make Point default-constructible, at which point you could write:

Array(std::initializer_list<Point> il)
{
    std::copy(il.begin(), il.begin() + std::min(il.size(), N),  _points.begin());
}

Or even without making Point default-constructible, you could use the index sequence trick to initialize with the help of a delegating constructor:

public:
    Array(std::initializer_list<Point> il)
        : Array(il, std::make_index_sequence<N>{})
    { }

private:
    template <size_t... Is>
    Array(std::initializer_list<Point> il, std::index_sequence<Is...> )
        : _points{{(Is < il.size() ? il.begin()[Is] : Point(0,0))...}}
    { }
like image 88
Barry Avatar answered Oct 19 '22 10:10

Barry