Порядок оценки при инициализации

В следующей программе:

#include <iostream>
struct I {
int i;
I(){i=2;}
I(int _i){i=_i;}
};
int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};
I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};
int main(int argc, char **argv) {
for (int b : a) std::cout << b << ' ';
std::cout << '\n';
for (auto &bb : aa) for (auto &b : bb) std::cout << b << ' ';
std::cout << '\n';
for (auto &B : A) std::cout << B.i << ' ';
std::cout << '\n';
for (auto &BB : AA) for (auto &B : BB) std::cout << B.i << ' ';
std::cout << '\n';
return 0;
}

Выход

1 0 0
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2

от http://ideone.com/1ueWdK с лязгом 3.7

но результат таков:

0 0 1
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2

на http://rextester.com/l/cpp_online_compiler_clang также с лязгом 3.7.

На моем собственном Ubuntu, GCC 6.2 дает внутреннюю ошибку компилятора на конструкции int aa[3][3] = {aa[2][2] = 1},

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

Вопрос в том:

Влияет ли порядок оценки побочных эффектов на назначение в списке инициализатора (например, a[2] = 1) и инициализация фактического элемента массива (например, a[2]) определено в стандарте?

Это явно указано как определенное или неопределенное? Или оно становится неопределенным только потому, что оно не определено явно?

Или конструкция имеет определенное или неопределенное поведение по другой причине, кроме порядка оценки?

5

Решение

Начнем с самого простого случая:

I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};

Оба из них — UB, из-за нарушения [basic.life]. Вы получаете доступ к значению объекта до начала его жизни. I не имеет тривиального конструктора по умолчанию и, следовательно, не может быть инициализирован в вакууме. Следовательно, время жизни объекта начинается только после завершения работы конструктора. Элементы A массив еще не был создан, когда вы обращаетесь к элементам этого массива.

Следовательно, вы вызываете UB, обращаясь к еще не сконструированному объекту.

Теперь два других случая более сложны:

int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};

Увидеть, int разрешает «пустую инициализацию», как определено в [basic.life] / 1. Хранение для a а также aa был приобретен. Следовательно, int a[3] является допустимым массивом int объекты, хотя агрегатная инициализация еще не началась. Таким образом, доступ к объекту и даже установка его состояния — это не UB.

Порядок операций здесь фиксирован. Даже до C ++ 17 инициализация элементов списка инициализатора выполняется до вызова инициализации агрегата, как указано в [dcl.init.list] / 4. Элементы в совокупности, которые здесь не перечислены в списке инициализации, будут заполнены, как если бы typename{} строит. int{} означает инициализировать значение int, что приводит к 0.

Так что, даже если вы установите a[2] а также aa[2][2], они должны быть немедленно перезаписаны с помощью агрегатной инициализации.

Следовательно, все эти компиляторы не правы. Ответ должен быть:

1 0 0
1 0 0 0 0 0 0 0 0

Разумеется, все это очень глупо, и вы не должны этого делать. Но с чисто языковой точки зрения это четко определенное поведение.

4

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector