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?
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.
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.
The initializer list is used to directly initialize data members of a class. An initializer list starts after the constructor name and its parameters.
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.
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 Point
s, that just can't work.
The only way to take a braced-init-list to mean an arbitrary amount of T
s (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
Point
s, 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 Point
s. That constructor is thus a non-template that takes precisely N
Point
s 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))...}}
{ }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With