Имеются ли точки последовательности в списках инициализации, когда они применяются к конструкторам?
В соответствии со стандартным документом 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", по-видимому, игнорируя эту часть стандарта и не вставляя точки последовательности.
Что мне здесь не хватает?
Примечание. Следующие вопросы относятся к этому вопросу:
Ответы
Ответ 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
Как вы можете видеть, в случае фигурных скобок предложения вычисляются в порядке появления под обоими компиляторами, что соответствует предоставленной вами заметке.