Home > Software engineering >  How to initialize two std::arrays in constructor initializer list when second depends on first
How to initialize two std::arrays in constructor initializer list when second depends on first

Time:02-20

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::vectors - 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_ts, 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.

  • Related