Предоставляет ли стандарт С++ нулевую инициализацию объекта POD с константными членами?

Я определил POD, который я планирую использовать в качестве неизменяемого хранилища данных. Чтобы добиться этого, я определил его членов как const, и я ожидаю инициализировать значение-инициализацию экземпляров (и в некоторых случаях нуль-инициализировать). Рассмотрим следующий код:

struct Foo
{
    const int value;
};

int main()
{
    Foo foo{ };

    return 0;
}

Когда я пытаюсь выполнить инициализацию этого POD с нулевой инициализацией, я получаю ошибку компилятора в Visual Studio (C3852) из-за квалификатора const на Foo::value. Если я удалю классификатор, код компилируется нормально.

Точное сообщение об ошибке:

ошибка C3852: 'Foo:: value' с типом 'const int': инициализация агрегата не может инициализировать этот член const не могут быть инициализированы по умолчанию, если их тип не имеет пользовательского конструктора по умолчанию

В соответствии со стандартом (проект n3337), §8.5/5 (нулевая инициализация):

Для нулевой инициализации объекта или ссылки типа T означает:

- если T - скалярный тип (3.9), объект устанавливается в значение 0 (ноль), взятое как интегральное постоянное выражение, преобразован в T;

- если T является (возможно, cv-квалифицированным) классом неединичного класса, каждый нестатический член данных и каждый базовый класс подобъект инициализируется нулем, а заполнение инициализируется нулевыми битами;

- если T является (возможно, cv-квалифицированным) объединенным типом, объекты сначала нестатического именованного элемента данных ноль инициализируются и заполнение инициализируется нулевыми битами;

- если T - тип массива, каждый элемент инициализируется нулем;

- если T является ссылочным типом, инициализация не выполняется.

и §8.5/6 (инициализация по умолчанию):

Для инициализации объекта типа T по умолчанию:

- если T является (возможно, cv-квалифицированным) классом типа (раздел 9), вызывается конструктор по умолчанию для T (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию);

- если T - тип массива, каждый элемент инициализируется по умолчанию;

- в противном случае инициализация не выполняется. Если программа требует инициализации по умолчанию объекта типа, определенного с помощью const, T должен быть классом типа с предоставленным пользователем конструктором по умолчанию.

и §8.5/7 (инициализация значения):

Для инициализации объекта типа типа T означает:

- если T является (возможно, cv-квалифицированным) классом типа (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T (и инициализация плохо сформирована, если T не имеет доступного значения по умолчанию Конструктор);

- если T является (возможно, cv-квалифицированным) классом типа non-union без предоставленного пользователем конструктора, тогда объект инициализируется нулем и, если Ts неявно объявленный конструктор по умолчанию является нетривиальным, этот конструктор называется.

- если T - тип массива, то каждый элемент инициализируется значением;

- в противном случае объект инициализируется нулем.

Мое чтение стандарта заставляет меня поверить, что мой POD должен быть инициализирован нулем; не инициализируется по умолчанию. Я неправильно понимаю процесс инициализации, описанный в стандарте?

РЕДАКТИРОВАТЬ: Учитывая детали, представленные в принятом ответе и связанных комментариях, это выглядит как потенциальная ошибка в реализации VS (то есть реализация может основываться на устаревшей версии стандарта), Я создал билет Microsoft Connect для отслеживания этого, который можно найти здесь:

https://connect.microsoft.com/VisualStudio/feedback/details/846222/c-compiler-uses-incorrect-initialization-scheme-for-certain-objects

Ответ 1

@dyp комментарий, что агрегатная инициализация происходит правильно, но приводит к нулевой инициализации элемента.

Foo foo{ };

- инициализация списка, поэтому мы начинаем с 8.5.4. 8.5.4p3 говорит, что (заказывающий из проекта n3690, который соответствует С++ 11, и все еще в n3797)

Список-инициализация объекта или ссылки типа T определяется следующим образом:

  • Если T является агрегатом, выполняется агрегатная инициализация (8.5.1).
  • В противном случае, если в списке инициализаторов нет элементов, а T - это тип класса с конструктором по умолчанию, объект инициализируется значением.
  • В противном случае, если T является специализацией std::initializer_list<E>, объект prvalue initializer_list строится, как описано ниже, и используется для инициализации объекта в соответствии с правилами инициализации объекта из класса того же типа (8.5).
  • В противном случае, если T - тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены и лучший выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если сужение конверсии (см. ниже) требуется для преобразования любого из аргументов, программа плохо сформирована.
  • В противном случае, если в списке инициализаций есть один элемент типа E, и либо T не является ссылочным типом, либо ссылочный тип ссылается на E, объект или ссылка инициализируются из этого элемента; если для преобразования элемента в T требуется преобразование сужения (см. ниже), программа плохо сформирована.
  • В противном случае, если T является ссылочным типом, временное значение praleue типа, на которое ссылается T, является инициализированным списком-списком или инициализированным прямым списком, в зависимости от типа инициализации для ссылки, и ссылка привязана к этому временному.
  • В противном случае, если в списке инициализаторов нет элементов, объект инициализируется значением.
  • В противном случае программа плохо сформирована.

В первом случае мы должны посетить 8.5.1 для агрегатной инициализации класса. p7:

Если в списке меньше предложений-инициализаторов, чем в совокупности, то каждый член, явно не инициализированный, должен быть инициализирован из своего элемента с равным-равным-инициализатором или, если нет скобок или равно -initializer из пустого списка инициализаторов (8.5.4).

Таким образом, член value получает список-инициализацию из пустого списка инициализаторов, который переписывается в указанное выше правило, достигая предпоследнего случая - инициализации значения. И, конечно, из правил в вопросе, это нулевая инициализация.


dyp упоминает, что до n3485, который реализовал CWG 1301, правило для построения по умолчанию имеет приоритет и будет пытаться и не получить доступ к удаленный конструктор по умолчанию.