Как инициализировать std::map с массивом структур внутри?

Есть такой код:

#include <vector>
#include <map>

struct Cell { // Простая структурка
    Cell(int x, int y) : X(x), Y(y) {}

    int8_t X = 0;
    int8_t Y = 0;
};

Cell q[4]({0, 0}, {0, 0}, {0, 0}, {0, 0}); // Здесь всё хорошо
Cell w[4] = {Cell(1, 1), Cell(1, 1), Cell(1, 1), Cell(1, 1)}; // И здесь всё хорошо

std::map <uint8_t, Cell> testMap{ // И так всё предсказуемо работает
    {0, {0, 0}},
    {1, {1, 1}},
    {2, {2, 2}}
};

std::map <uint8_t, Cell[4]> figureDictionary{ // А здесь ругается
    {0, {{0, 0}, {0, 0}, {0, 0}, {0, 0}}},
    {1, {{0, 0}, {0, 0}, {0, 0}, {0, 0}}}
};

std::map <uint8_t, Cell[4]> figureDictionary2 = { // И здесь ругается
    {0, {Cell(0, 0), Cell(0, 0), Cell(0, 0), Cell(0, 0)}},
    {1, {Cell(1, 1), Cell(1, 1), Cell(1, 1), Cell(1, 1)}}
};

Говорит, отсутствуют экземпляры конструктора, соответствующие etc.. Хотя по идее он мог бы развернуть скобки довольно однозначно.

Отчего так? MSVS 2022, С++20 (если это вдруг важно)

UPD. 5.12.23

По совету попробовал воспользоваться std::array или std::vector. Картина любопытная - простая замена одного массива на другой результата не даёт, конструкторы в них по-разному выглядят (как я это понимаю)

А вот когда изладил код на std::array - попробовал поменять его обратно на простой встроенный массив - и всё заработало почему-то:

#include <array>
#include <map>

struct Cell {
    Cell(int x, int y) : X(x), Y(y) {}

    int8_t X = 0;
    int8_t Y = 0;
};

/*
std::array<Cell, 4> q0{ Cell(0, 0), Cell(0, 1), Cell(-1, 0), Cell(-1, 1) };
std::array<Cell, 4> q1{ Cell(1, 0), Cell(0, 0), Cell(0, 1), Cell(-1, 0) };
std::array<Cell, 4> q2{ Cell(1, 0), Cell(0, 0), Cell(-1, 0), Cell(-1, 1) };
std::array<Cell, 4> q3{ Cell(1, 0), Cell(0, 0), Cell(-1, 0), Cell(-1, -1) };
std::array<Cell, 4> q4{ Cell(1, 0), Cell(0, 0), Cell(-1, 0), Cell(-2, 1) };
*/

Cell q0[4] { Cell(0, 0), Cell(0, 1), Cell(-1, 0), Cell(-1, 1) };
Cell q1[4] { Cell(1, 0), Cell(0, 0), Cell(0, 1), Cell(-1, 0) };
Cell q2[4] { Cell(1, 0), Cell(0, 0), Cell(-1, 0), Cell(-1, 1) };
Cell q3[4] { Cell(1, 0), Cell(0, 0), Cell(-1, 0), Cell(-1, -1) };
Cell q4[4] { Cell(1, 0), Cell(0, 0), Cell(-1, 0), Cell(-2, 1) };

const std::map <uint8_t, std::array <Cell, 4>> figureDictionary = {
    {0, {q0[0], q0[1], q0[2], q0[3]}},
    {1, {q1[0], q1[1], q1[2], q1[3]}},
    {2, {q2[0], q2[1], q2[2], q2[3]}},
    {3, {q3[0], q3[1], q3[2], q3[3]}},
    {4, {q4[0], q4[1], q4[2], q4[3]}}
};

В принципе мои задачи такой вариант закрывает, однако если кто-то расскажет, почему и отчего так происходит - будет здорово


Ответы (1 шт):

Автор решения: Pak Uula

Короткий ответ: никак.

Подробнее.

Конструктор

map( std::initializer_list<value_type> init,
     const Compare& comp = Compare(),
     const Allocator& alloc = Allocator() );

принимает на вход список инициализаторов для типа map::value_type, то есть pair<const K, V>. В вашем случае это pair<const uint8_t, Cell[4]>.

Поэтому ваш вопрос сводится с другому - можно ли инциализировать пару, содержащую массив фиксированной длины?

Пробуем:

typedef std::pair <const uint8_t, Cell[4]> Pair;
Pair p{0, w};

получаем отлуп.

Если отвлечься от того, что пишет компилятор, и посмотреть справочник, то окажется, что нужный конструктор pair(const uint8_t&, const Cell (&)[4]) вызывается при условии

std::is_copy_constructible_v<const uint8_t> == true && 
std::is_copy_constructible_v<Cell[4]> == true

Пробуем:

int main() {
    std::cout << std::is_copy_constructible_v<const uint8_t> << std::endl;
    std::cout << std::is_copy_constructible_v<Cell[4]> << std::endl;

    return 0;
}

получаем

1
0

Вот и ответ - массив в С++ не имеет констуктора копирования. Поэтому нельзя в конструкторе pair(const T1& x, const T2& y) : first(x), second(y) {} инициализировать поле second в случае, когда T2 является массивом Cell[4].

Аналогичная проблема с конструктором, который "двигает" параметры: pair(const uint8_t&&, Cell (&&)[4]) вызывается при условии

std::is_move_constructible_v<const uint8_t> == true && 
std::is_move_constructible_v<Cell[4]> == true

Пробуем:

void main() {
    std::cout << std::is_move_constructible_v<const uint8_t> << std::endl;
    std::cout << std::is_move_constructible_v<Cell[4]> << std::endl;
}

получаем

1
0

Другими словами, поле second в паре pair<const uint8_t, Cell[4]> не инициализировать ни копированием, ни сдвигом.

Почему в таком случае работает инициализатор для map<uint8_t,array<Cell, 4>>? Для класса array<Cell, 4> список {q0[0], q0[1], q0[2], q0[3]} является инициализатором, из него конструируется объект типа std::array<Cell, 4>. Так как у этого типа есть и конструктор копирования, и конструктор сдвига, то для каждой из пар в инциализаторе map вызывается конструктор pair(const uint8_t&& x, array<Cell,4>&& y ), и компилятор не ругается.

→ Ссылка