Как инициализировать 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 шт):
Короткий ответ: никак.
Подробнее.
Конструктор
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 ), и компилятор не ругается.