Почему для инициализации массива пар по-прежнему нужны двойные фигурные скобки в С++ 14?
В стандарте С++ 14 инициализация std::array
может выполняться с единственными фигурными скобками (см. Http://en.cppreference.com/w/cpp/container/array):
Это, однако, не работает для std::array
из std::pair
.
Почему эти работы:
std::pair<int, int> p { 1, 2 };
std::array<int, 3> a {1, 2, 3};
но это не работает:
std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};
в то время как это снова работает?
std::array<std::pair<int, int>, 3> b {{{1, 11}, {2, 22}, {3, 33}}};
Кроме того, для завершения инициализация старого старого массива работает с одиночными скобками
std::pair<int, int> c[3] {{1, 11}, {2, 22}, {3, 33}};
Ответы
Ответ 1
Кажется, это парсинг-двусмысленность, несколько похожая на знаменитый самый неприятный синтаксический разбор. Я подозреваю, что происходит:
Если вы пишете
std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};
компилятор имеет два способа интерпретировать синтаксис:
-
Вы выполняете инициализацию с полной привязкой (это означает, что внешняя скобка ссылается на агрегатную инициализацию std::array
, тогда как первая внутренняя инициализирует внутреннее представление элемента std::array
которое является реальным C-массивом). Это не скомпилируется, так как std::pair<int, int>
впоследствии не может быть инициализирован 1
(все фигурные скобки израсходованы). clang даст ошибку компилятора, указывающую на то, что:
error: no viable conversion from 'int' to 'std::pair<int, int>'
std::array<std::pair<int, int>, 3> a{{1, 11}, {2, 22}, {3, 33}};
^
Обратите внимание, что эта проблема разрешена, если не существует внутренней инициализации внутреннего члена, т.е.
std::pair<int, int> b[3] = {{1, 11}, {2, 22}, {3, 33}};
будет скомпилироваться просто как совокупная инициализация.
-
(То, как вы это имели в виду.) Вы выполняете инициализацию с фиксацией с помощью скобок, поэтому самые внутренние фигурные скобки предназначены для агрегатной инициализации отдельных пар, а фигурные скобки для внутренних представлений массива устранены. Обратите внимание, что даже если бы не была эта двусмысленность, как правильно указано в ответе rustyx, правила выравнивания фигурных скобок не применяются, поскольку std::pair
является агрегированным типом, поэтому программа все равно будет плохо сформирована.
Компилятор предпочтет вариант 1. Предоставляя дополнительные фигурные скобки, вы выполняете инициализацию с полной привязкой и снимаете любую синтаксическую двусмысленность.
Ответ 2
Правило выравнивания фигур С++ 14 применяется только для субагрегации инициализации.
Так, например, что-то вроде этого работает:
std::array<std::array<int, 3>, 3> a{1, 11, 2, 22, 3, 33};
Здесь совокупность агрегатов может быть инициализирована списком без дополнительных привязок.
Но std::pair
не является агрегатом (у него есть конструкторы), поэтому правило не применяется.
Это означает, что без правила выравнивания фигурной скобки std::array
, сам являющийся агрегатом с массивом внутри, нуждается в дополнительном наборе фигурных скобок для инициализации списка. Помните, что array
шаблонов классов реализован как:
template<typename T, std::size_t N>
struct array {
T elems[N];
};
Чтобы перечислить-инициализировать его без правила elems
фигурных скобок, вам понадобится дополнительный набор фигурных скобок для elems
к elems
.
Ответ 3
Без двойных фигурных скобок утверждение просто неоднозначно. Рассмотрим следующий код:
std::array<std::pair<int, int>, 1> a = {{ {1, 2} }};
std::array<int, 2> b = { {1, 2} };
Без двойных фигурных скобок в первом определении компилятор будет рассматривать { {1,2} }
как скалярный список инициализации для array<int, 2>
. Вам нужно объявить явный вложенный список с расширенными командами, чтобы компилятор мог распознать, что внутренний список также инициализируется агрегатом (по сравнению с инициализированным скаляром), так что он может построить массив std::pair
.
Ответ 4
В теории std::array
следует инициализировать с помощью агрегатной инициализации. Так на самом деле это:
std::array<int, 3> a {1, 2, 3};
является синтаксическим сахаром для этого:
std::array<int, 3> a {{1, 2, 3}};
Как вы видите, в первом кажется, что я инициализирую массив со значениями, но он действительно представляет собой совокупную инициализацию с расширенным списком инициализации. Это ясно, как день во второй ситуации. Так что для стартеров.
Хорошо, так почему же это не работает?
std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};
Ну, просто поместите - компилятор не может отличить, какой тип синтаксиса вы используете для инициализации массива. {1, 11}
можно интерпретировать как как список инициализаторов, так и использовать первую версию или ее можно интерпретировать как пару и перейти со второй версией.
Этот код:
std::array<std::pair<int, int>, 3> b {{{1, 11}, {2, 22}, {3, 33}}};.
устраняет двусмысленность.
Источник: http://en.cppreference.com/w/cpp/language/aggregate_initialization
Ответ 5
Я собираюсь угадать здесь.
Список инициализаторов для std::array<T,n>
должен быть списком T
(или тривиально конструктивным для T
). Таким образом, вы могли бы сделать
std::array<std::pair<int,int>,3> b { std::pair{1,11}, std::pair{2,22}, std::pair{3,33} };
но это утомительно многословно. Чтобы получить преобразование в std::pair<int,int>
вам нужен, вам необходимо предоставить список инициализаторов, поэтому
std::array<std::pair<int,int>,3> b {
{ // element 1
{ // initialize from:
{ 1,11 } // std::initializer_list
}
},
...
};
Я не могу защитить это дальше, но обратите внимание, что std::vector<T, Allocator>::vector( std::initializer_list<T>, const Allocator& alloc=Allocator())
определен, но std::array<T,n>::array( std::initializer_list<T> )
- нет. Также не определена std::pair<U,T>::pair( std::initializer_list<??> )
.