Of the various ways to initialize std::array
class members in the constructor initializer list, I found the variadic parameter pack to work best.
However, how can I initialize a second std::array
member which depends on the first?
The example here is a struct polygon
which takes a variadic parameter pack of Vector2
vertices. From those vertices we also construct the lines. These lines store the vertices again (references or pointers seem unnecessary given the typical 16byte size of a Vector2) in pairs of line.start
and line.end
and also pre-compute the Length and normal vector of the line for performance.
Note: I don't want to use two std::vector
s - that's what the working code has already.
Is there a way to move the logic from the constructor body to the constructor initializer list for this case?
template <unsigned N, typename Number = double>
struct polygon {
std::array<Vector2<Number>, N> vertices;
std::array<Line<Number>, N> lines; // this is calling Line's default constructor: TOO EARLY
template <typename... Pack>
polygon(Pack... vs)
: vertices{vs...} /* , lines{ .... ??? } this is where we need to construct / initialize lines */ {
static_assert(sizeof...(vs) == N, "incorrect number of vertices passed");
for (auto i = 0UL; i != vertices.size(); i) {
// this is calling Line's copy assignment operator: TOO LATE
lines[i] = Line(vertices[i], vertices[(i 1) % vertices.size()]);
}
}
// ...
}
using Vec2 = Vector2<double>;
using triangle = polygon<3>;
using quadrilateral = polygon<4>;
using pentagon = polygon<5>;
int main() {
auto tri = triangle(Vec2{1, 2}, Vec2{3, 4}, Vec2{4, 5});
std::cout << tri << "\n";
// this would be even nicer, but doesn't work: "substitution failure: deduced incomplete pack"
// auto tri = triangle({1, 2}, {3, 4}, {4, 5});
}
CodePudding user response:
You can put patterns in front of a ...
, not just the pack. However, this requires a suitable pack. In this case, a useful pack would be the indices, so you can make a helper for that:
template<std::size_t... Is>
auto make_lines(std::index_sequence<Is...>) {
return std::array<Line<Number>, N>{
Line(vertices[Is], vertices[(Is 1) % vertices.size()])...
};
}
std::index_sequence
is simply a standard holder type for a bunch of std::size_t
s, no behaviour of its own.
Then you create the pack when calling it:
polygon(Pack... vs)
: vertices{vs...},
lines(make_lines(std::index_sequence_for<Pack...>{})) { ... }
std::index_sequence_for
takes the elements of Pack
and creates a sequence 0, 1, 2, ..., N-1
, one per element received. That is, it's a sequence from 0 to sizeof...(Pack) - 1
, perfect for indices.