Является ли изменение переменной в ее заявлении корректным образом?

Например:

#include<iostream>
using namespace std;
int main() {
    int i = i=0; //no warning
    cout << i << endl;
    return 0;
}

Скомпилирован в vs2015 без предупреждения и вывода 0. Является ли этот фрагмент кода хорошо определенным, хотя кажется немного странным?

Однако в этом онлайн-компиляторе (g++ prog.cc -Wall -Wextra -std=c++17) он выдает предупреждение:

prog.cc: In function ''int main()'':  
prog.cc:8:12: warning: operation on ''i'' may be undefined [-Wsequence-point]
     'int i=i=0;'

Ответы

Ответ 1

Возможны два случая, в зависимости от того, началось ли время жизни объекта. Это определяется первым правилом в [basic.life]:

Время жизни объекта или ссылки является временем выполнения объекта или ссылки. Говорят, что объект имеет непустую инициализацию, если он относится к классу или агрегату, и он или один из его подобъектов инициализируется конструктором, отличным от тривиального конструктора по умолчанию. [Примечание. Инициализация тривиальным конструктором copy/move является пустой функцией инициализации. - end note] Время жизни объекта типа T начинается, когда:

  • хранения с надлежащим выравниванием и размером для типа T, и
  • если объект имеет незапамятную инициализацию, его инициализация завершена, за исключением того, что если объект является членом объединения или его подобъектом, его время жизни начинается только тогда, когда этот член объединения является инициализированным членом в объединении или как описано в ([class.union]).
  1. Объекты класса или совокупного типа

    std::string s = std::to_string(s.size()); // UB
    

    В этом случае время жизни объекта не начинается до завершения инициализации, поэтому это правило в [basic.life] применимо:

    Аналогично, до того, как началось время жизни объекта, но после того, как хранилище, которое будет занимать объект, было выделено или, после того, как срок жизни объекта закончился и перед хранилищем, которое объект занят, повторно используется или выпущен, любое значение gl, которое ссылается на исходный объект может использоваться, но только ограниченным образом. Для объекта, находящегося в процессе строительства или уничтожения, см. ([class.cdtor]). В противном случае такое значение glvalue относится к выделенному хранилищу, и использование свойств glvalue, которые не зависят от его значения, хорошо определено. Программа имеет неопределенное поведение, если:

    • glvalue используется для доступа к объекту или
    • glvalue используется для вызова нестатической функции-члена объекта или
    • glvalue привязан к ссылке на виртуальный базовый класс или
    • glvalue используется как операнд dynamic_cast или как операнд typeid.

    В этом примере glvalue используется для доступа к нестационарному члену, что приводит к неопределенному поведению.

  2. Объекты примитивного типа

    int i = (i=0); // ok
    int k = (k&0); // UB
    

    Здесь, хотя есть инициализатор, инициализация не может быть непустой из-за типа. Таким образом, время жизни объекта запустилось, и приведенное выше правило не применяется.

    Тем не менее, существующее значение в объекте неопределенно (если объект не имеет статической продолжительности хранения, и в этом случае статическая инициализация дает ему значение нуля). Значение gl, относящееся к объекту с неопределенным значением, никогда не должно подвергаться преобразованию lvalue-to-rvalue. Таким образом, разрешены операции "только для записи", но большинство 1 операций, считывающих неопределенное значение, приводят к неопределенному поведению.

    Соответствующее правило находится в [dcl.init]:

    Если для объекта не задан инициализатор, объект инициализируется по умолчанию. Когда хранилище для объекта с автоматической или динамической продолжительностью хранения получается, объект имеет неопределенное значение, и если для объекта не выполняется инициализация, этот объект сохраняет неопределенное значение до тех пор, пока это значение не будет заменено. [Примечание. Объекты со статикой или длительностью хранения потоков ноль-инициализируются, см. ([basic.start.static]). - конечная нота]

    Если неопределенное значение создается путем оценки, поведение не определено, за исключением следующих случаев:

    • Если неопределенное значение беззнакового типа узкого символа или типа std::byte создается путем оценки:

      • второй или третий операнд условного выражения,
      • правый операнд запятой,
      • операнд приведения или преобразование в неподписанный узкосимвольный тип или std::byte type или
      • выражение с отбрасываемой величиной,

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

    • Если неопределенное значение беззнакового типа узкого символа или типа std::byte создается путем оценки правильного операнда простого оператора присваивания, первым операндом которого является lvalue беззнакового узкого символьного типа или типа std :: byte, неопределенное значение заменяет значение объекта, на которое ссылается левый операнд.
    • Если неопределенное значение беззнакового типа узкого символа создается путем вычисления выражения инициализации при инициализации объекта беззнакового узкого символьного типа, этот объект инициализируется неопределенным значением.
    • Если неопределенное значение беззнакового типа узкого символа или типа std::byte создается путем оценки выражения инициализации при инициализации объекта типа std::byte, этот объект инициализируется неопределенным значением.

1 Существует узкое исключение для использования типов символов для копирования неопределенных значений, что делает целевое значение также неопределенным. Значение по-прежнему не может использоваться в других операциях, таких как побитовые операторы или арифметические операции.

Ответ 2

Изменяет ли переменная переменную в ее декларации корректно?

int i = i=0;//no warning

Вышеприведенная инструкция инициализируется и четко определена, так как два i находятся в одной области.

Согласно basic.scope.pdecl # 1

Точка объявления для имени сразу же после его полного декларатора и перед его инициализатором (если есть), за исключением случаев, указанных ниже. [ Пример:

unsigned char x = 12; // Warning -Wunused-variable
{ unsigned char x = x; }
                ^ warning -Wuninitialized

Здесь второй x инициализируется собственным (неопределенным) значением. - конец примера]

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

warning: 'x' is used uninitialized in this function [-Wuninitialized]

Учитывая тот факт, что локальные переменные с автоматическим хранилищем будут иметь неопределенное значение, если не инициализированы, я считаю, что есть задание, имеющее этот порядок.

int (i = (i = 0));

пример

int x; // indeterminate
int i = i = x = 2; // x is assigned to two, then x value is assigned to i
cout << x << " " << i; // i = 2, x = 2