Подтвердить что ты не робот

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

В соответствии со стандартным документом n4296 С++:

[dcl.init.list] (8.5.4.4) (pg223-224)

В списке инициализаторов списка с привязкой-инициализацией инициализатор-предложения, в том числе любые из результатов расширения пакета (14.5.3), оцениваются в том порядке, в котором они появляются. То есть, вычисление каждого значения и побочный эффект, связанный с данным Параметр инициализатор секвенирован перед каждым вычислением значения и побочный эффект, связанный с любым предложением инициализатора, которое следует за ним в список списков инициализаторов, разделенных запятыми. [Примечание: это порядок оценки выполняется независимо от семантики инициализация; например, оно применяется, когда элементы initializer-list интерпретируются как аргументы вызова конструктора, даже если обычно нет ограничений последовательности на аргументы вызова. -end note]

(акцент мой)

Примечание было добавлено здесь: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1030

Это говорит мне, что следующий код:

#include <iostream>

struct MyType {
  MyType(int i, int j, int k, int l)
    : sum(i + j + k + l)
  {

  }

  int sum;
};

int main()
{
  int i = 0;
  std::cout << MyType{ ++i, ++i, ++i, ++i }.sum << '\n';
}

Должен напечатать "10".

Это мои рассуждения:

  • MyType инициализируется через список с привязкой
  • braced-init-lists оцениваются в порядке
  • даже если он интерпретируется как аргументы вызова конструктора
  • это означает, что он должен быть оценен как MyType (1,2,3,4)

То есть приведенный выше код должен вести себя точно так же, как этот код:

#include <initializer_list>
#include <iostream>

int main()
{
  int i = 0;
  std::initializer_list<int> il{++i, ++i, ++i, ++i};
  std::cout << *il.begin() + *(il.begin() + 1) + *(il.begin() + 2) + *(il.begin() + 3) << '\n';
}

Но это не так. Первый пример печатает "16", а второй пример печатает "10"

Буквально каждый компилятор от каждого поставщика, который я могу получить отпечатков "16", по-видимому, игнорируя эту часть стандарта и не вставляя точки последовательности.

Что мне здесь не хватает?

Примечание. Следующие вопросы относятся к этому вопросу:

4b9b3361

Ответ 1

Ответ кажется, что да, это ошибка как в GCC, так и в MSVC.

Это статус этой проблемы:

  • Существует несколько ошибок против GCC относительно правил списка инициализации. Большинство из них полностью отказались от команды GCC. Это по крайней мере подразумевает, что у g++ есть ошибки здесь, потому что проблемы не были закрыты как недопустимые
  • Я получил неофициальное слово от команды компилятора MSVC, что на самом деле это ошибка в их компиляторе, и они работают внутри, чтобы исправить это. Однако у меня нет внешней ошибки. Начиная с версии MSVC 2015 Update 3, старое поведение остается.
  • Clang, который на данный момент является самым педантичным стандартом - компилятором жалобы, реализует его так, как кажется, читается стандарт.

Мое личное исследование, обсуждения с экспертами на С++ на конференциях и неофициальные ответы, полученные мной разработчиками компилятора, указывают на то, что это ошибка в MSVC и GCC, но я всегда неохотно отвечаю на собственные вопросы в StackOverflow. Но мы здесь.

Ответ 2

Эта заметка не связана с порядком оценки. Как было сказано в одном из комментариев, это о порядке преобразования фактических параметров в значения и стандарт не определяет такой порядок. Вы должны получить следующее предупреждение (gcc):

17:58: warning: operation on 'i' may be undefined [-Wsequence-point]

Я немного модифицировал программу, чтобы продемонстрировать, как оценка аргументов работает с {} и().

При такой модификации программа не зависит от порядка преобразования lvalue в rvalue, поэтому не имеет двусмысленности, которая вас разочаровала.

#include <iostream>

struct MyType {
  MyType(int i, int j)
    : sum(i + j)
  {

  }

  int sum;
};

int main()
{
  int i = 0;
  int a,b;
  std::cout << MyType{ (a = ++i), (b = ++i) }.sum << '\n';
  std::cout << "Here clauses are evaluated in order they appear: a=" << a << ", b=" << b << std::endl;
  i = 0;
  std::cout << MyType( (a = ++i), (b = ++i) ).sum << '\n';
  std::cout << "Here order of evaluation depends on implementation: a=" << a << ", b=" << b << std::endl;
}

И выход этой программы для clang и gcc:

лязг:

3
Here clauses are evaluated in order they appear: a=1, b=2
3
Here order of evaluation depends on implementation: a=1, b=2

НКА:

3
Here clauses are evaluated in order they appear: a=1, b=2
3
Here order of evaluation depends on implementation: a=2, b=1

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