How do I generate an initializer list for a variadic arguments constructor from an std::array

56 views Asked by At

As a simplification, suppose we have two different kinds of pins and a Board that holds one of them

// Pin for Board
struct BoardPin { };

// another kind of pin
struct SpecialPin { };

// a Board that comes with a Pin
class Board
{
    // Board's pin
    BoardPin m_pin;

public:
    // get the Board's pin
    constexpr BoardPin &get_pin()
    {
        return m_pin;
    }
};

And a variadic box of pins that we'll need that can hold arbitrary pins:

// a box of pins of any kind
template <typename... PinTypes>
class PinBox
{
    std::tuple<PinTypes...> m_pins;

public:
    // construct a PinBox with any kinds of pins
    explicit PinBox(PinTypes &...pins) : m_pins{std::tie(pins...)} {};
};

And finally, the room that contains everything and wherein all the activities happen

// a BoardRoom has several Boards
// and a SpecialPin
class BoardRoom
{
    // number of boards a room has
    static constexpr size_t M_NUM_BOARDS = 4;

    // the BoardRoom's Boards
    std::array<Board, M_NUM_BOARDS> m_boards;

    // the BoardRoom's SpecialPin
    SpecialPin m_special_pin;

    // get the SpecialPin
    constexpr SpecialPin& get_special_pin() {
        return m_special_pin;
    }

public:
    // do something that require a PinBox of all the pins
    void do_something();
};

The question pertains to the definition of BoardRoom::do_something() where we have to construct a PinBox

void BoardRoom::do_something() {
    PinBox box {
        get_special_pin(),
        m_boards[0].get_pin(),
        m_boards[1].get_pin(),
        m_boards[2].get_pin(),
        m_boards[3].get_pin()
    };

    // other stuff
}

How can I construct that PinBox with the result of get_special_pin() and all the elements of m_boards without having to write out a line for each pin in m_boards?

(Like I said, this is a simplification to focus on the actual problem: the actual definitions are spread across a dozen files in varying namespaces. A little context: the pins are mutexes, the Board is a Database object, the PinBox is std::scoped_lock and the BoardRoom is a "live" server object that has to force synchronization for consistency in implementing durability guarantees)

The problem here, really, is that definition of PinBox box is inflexible and would be unweidly if M_NUM_BOARDS grows large (like 1024; I currently have 16). Also it's error-prone as there's no bounds check on the handwritten std::array::operator[].

Is there a better way (that does not require modification of the PinBox definition everytime M_NUM_BOARDS changes)?

Of course, we can introduce a get_board_pin(int) like

// get a pin
constexpr BoardPin& get_board_pin(int idx) {
    return m_boards[idx].get_pin();
}

So that the construction becomes

PinBox box {
    get_special_pin(),
    get_board_pin(0),
    get_board_pin(1),
    get_board_pin(2),
    get_board_pin(3)
};

which seems like a step in the right direction, but I'm really not sure what happens next. Maybe templates?

Any solution, no matter how bulky, would be appreciated.

2

There are 2 answers

0
NathanOliver On BEST ANSWER

You can make a factory function that you can pass the first pin and the array to and then construct the object using an immediately invoked lambda function to create a pack of array indices that can be used to expand out the array into constructor parameters. That looks like:

template <typename Pin, std::size_t N>
auto make_pinbox(const Pin& special_pin, const std::array<Board, N>& boards)
{
    return [&]<std::size_t... Is>(std::integer_sequence<size_t, Is...>)
    {
        return PinBox{ special_pin, boards[Is].get_pin()... };
    }(std::make_index_sequence<N>{});
}
0
Kingsley Oyelabi On

I was able to simplify Nathan's answer (after a minor correction) to

auto BoardRoom::make_pinbox()
{
    return [this]<size_t... I>(std::integer_sequence<size_t, I...>)
    {
        return PinBox{ get_special_pin(), m_boards[I].get_pin()... };
    }(std::make_index_sequence<M_NUM_BOARDS>{});
}

which avoids making the make_pinbox() function a template, doesn't pass any parameters, and can be hidden as a private implementation detail in BoardRoom, which was exactly what I wanted!

do_something becomes a trivial

void BoardRoom::do_something() {
    auto box = make_pinbox();
    // other stuff
}