Почему инициализация массива пар все еще требует двойных скобок в C++14?


со стандартом C++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}};
5 57

5 ответов:

это, кажется, разбор амбугуиты несколько похож на знаменитый самый досадный разбор. Я подозреваю, что происходит вот что:

если вы пишите

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

компилятор имеет два способа интерпретировать синтаксис:

  1. вы выполняете полную инициализацию скобки (это означает, что самая внешняя скобка относится к агрегатной инициализации 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}};
    

    будет компилироваться просто отлично, как агрегатная инициализация.

  2. (так, как вы это имели в виду.) Вы выполните инициализацию с использованием фигурных скобок, поэтому самые внутренние фигурные скобки предназначены для агрегированной инициализации отдельных пар, а фигурные скобки для внутренних представлений массива являются elided. Обратите внимание, что даже если бы не было этой двусмысленности, как правильно отмечено в rustyx это!--10-->, правила расчалки elision не применяются как std::pair не является агрегатным типом, поэтому программа все равно будет плохо сформирована.

компилятор предпочтет вариант 1. От предоставляя дополнительные фигурные скобки, вы выполняете полную инициализацию скобок и устраняете любую синтаксическую неоднозначность.

C++14 brace elision rule применяется только к инициализации подагрегата.

так, например, что-то вроде это работает:

std::array<std::array<int, 3>, 3> a{1, 11, 2, 22, 3, 33};

здесь совокупность агрегатов может быть инициализирована списком без дополнительных фигурных скобок.

но std::pair - это не совокупность (это конструкторы), поэтому правило не действует.

что означает, что без Правила elision скобки,std::array, что в совокупности с массивом внутри, нужен дополнительный набор скобок для того, чтобы быть список-инициализации. Помните, что шаблон класса array реализуется как:

template<typename T, std::size_t N> 
struct array {
  T elems[N];
};

до list-инициализировать это без Правила elision скобки, вам нужен дополнительный набор скобок, чтобы добраться до elems член.

без двойных скобок это утверждение просто неоднозначно. Рассмотрим следующий код:

    std::array<std::pair<int, int>, 1> a = {{ {1, 2} }};
    std::array<int, 2> b = { {1, 2} };

без двойных скобок в первом определении, компилятор будет рассматривать { {1,2} } как скалярный список инициализации на array<int, 2>. Вам нужно объявить явное вложенные приготовился инициализации-список для того, чтобы компилятор распознал, что внутренний список также агрегатно-инициализации (против скалярной инициализации), так что он может построить массив std::pair.

в теории std::array должен быть инициализирован с агрегатной инициализации. Так что на самом деле это:

std::array<int, 3> a {1, 2, 3};

- это синтаксический сахар для этого:

std::array<int, 3> a {{1, 2, 3}};

как вы видите, в первом кажется, что я инициализирую массив со значениями, но это действительно агрегатная инициализация с привязанным init-list. Это ясно как день во второй ситуации. Так что это для начала.

хорошо, так почему же это не работает?

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

Я думаю здесь.
Список инициализаторов для 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<??> ) определенными.