Почему сужающее преобразование, используемое с инициализатором с разделителями в виде фигурных скобок, не вызывает ошибку?

Я узнал о инициализаторе с разделителями в виде фигурных скобок в языке программирования C ++, 4-е изд. > Глава 2: Тур по C ++: Основы.

Я цитирую из книги ниже.

Форма = традиционна и восходит к C, но в случае сомнений используйте общую форму {} -list (§6.3.5.2).
Если ничего другого, это спасает вас от конверсий, которые теряют информацию (сужение конверсий; §10.5):

int i1 = 7.2;    // i1 becomes 7
int i2 {7.2};    // error : floating-point to integer conversion
int i3 = {7.2};  // error : floating-point to integer conversion (the = is redundant)

Однако я не могу воспроизвести эти результаты.

У меня есть следующий код.

#include <iostream>

int main()
{
int i1 = 7.2;
int i2 {7.2};
int i3 = {7.2};

std::cout << i1 << "\n";
std::cout << i2 << "\n";
std::cout << i3 << "\n";
}

Когда я компилирую и запускаю его, я не получаю никакой ошибки. Я получаю предупреждение о std=c++11 но без ошибок.

$ g++ init.cpp
init.cpp: In function ‘int main()’:
init.cpp:6:12: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11
int i2 {7.2};
^
$ ./a.out
7
7
7

Кроме того, предупреждение относится только ко второму назначению, но нет предупреждения для третьего назначения. Это, кажется, указывает на то, что = на самом деле не является избыточным, как упомянуто в книге. Если = были бы избыточными, либо и второе, и третье назначения вызвали бы предупреждения, либо оба не дали бы предупреждения.
Затем я собираю их с -std=c++11 флаг.

$ g++ -std=c++11 init.cpp
init.cpp: In function ‘int main()’:
init.cpp:6:16: warning: narrowing conversion of ‘7.2000000000000002e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
int i2 {7.2};
^
init.cpp:7:18: warning: narrowing conversion of ‘7.2000000000000002e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
int i3 = {7.2};
^
$ ./a.out
7
7
7

Все еще нет ошибки. Только предупреждения. Хотя в этом случае второе и третье назначения ведут себя одинаково по отношению к генерации предупреждений.

Итак, мой вопрос: хотя в книге упоминается, что второе и третье присваивания являются ошибками, почему этот код не скомпилируется?

13

Решение

Это плохо сформировано и должно быть диагностическое, однако это может быть либо предупреждение (который вы получили) или ошибка. gcc сделал это предупреждение для нескольких версий из-за проблемы переноса с C ++ 03:

Стандарт требует только, чтобы «соответствующая реализация выдала как минимум одно диагностическое сообщение», поэтому компиляция программы с предупреждением разрешена. Как сказал Эндрю, -Werror = сужение позволяет вам сделать ошибку, если хотите.

G ++ 4.6 выдавал ошибку, но она была намеренно заменена на предупреждение для 4.7, потому что многие люди (включая меня) обнаружили, что сужение конверсий является одной из наиболее часто встречающихся проблем при попытке компилировать большие кодовые базы C ++ 03 как C ++ 11. Ранее правильно сформированный код, такой как char c [] = {i, 0}; (где я только когда-либо буду в пределах диапазона char) вызвал ошибки и должен был быть изменен на char c [] = {(char) i, 0}

но теперь последние версии gcc и clang делают это ошибкой, увидеть это в прямом эфире для GCC.

Для справки проект стандарта C ++ 11 раздел 8.5.4 [Dcl.init.list] говорит:

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

int x1 {2}; // OK
int x2 {2.0}; // error: narrowing

— конец примера]

а также:

Сужающее преобразование — это неявное преобразование

  • от типа с плавающей точкой до целочисленного типа, или
[…] [Примечание: как указано выше, такие преобразования не допускаются на верхнем уровне при инициализации списка. — конец
примечание] [Пример:

[…]
int ii = {2.0}; // error: narrows
[…]

Таким образом, преобразование с плавающей точкой в ​​целочисленное является сужающим преобразованием и является некорректным.

и раздел 1.4 Соответствие реализации [intro.compliance] гласит:

Хотя в этом международном стандарте указаны только требования к реализациям C ++, эти требования
часто легче понять, если они сформулированы как требования к программам, частям программ или
выполнение программ. Такие требования имеют следующее значение:

[…]
  • Если программа содержит нарушение какого-либо диагностируемого правила или вхождение конструкции, описанной в
    этот стандарт как «условно поддерживаемый», когда реализация не поддерживает эту конструкцию,
    соответствующая реализация должна выдать хотя бы одно диагностическое сообщение.
[…]

Говорит нам, что требуется только диагностика.

13

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

Язык C ++ не различает «предупреждения» от «ошибок». C ++ имеет только диагностические сообщения. Полученные предупреждения являются диагностическими сообщениями. Спецификация языка не требует от компиляторов прекращения компиляции, когда они сталкиваются с ошибочным (или плохо сформированным) кодом. Все, что нужно сделать компиляторам — это выдать диагностическое сообщение, а затем они могут продолжить компиляцию, если они того пожелают.

Это означает, что в общем случае вы обязаны сообщать предупреждения, которые являются «просто предупреждениями» из предупреждений, которые фактически указывают на подлинные ошибки, особенно с такими разрешающими компиляторами, как GCC.

Это также означает, что реальное поведение в реальной жизни зависит от настроек компилятора. Попросите вашего компилятора быть более строгим в этом отношении, если это возможно. В GCC вы можете попробовать -pedantic-errors переключиться для этой цели.

Постскриптум В моих экспериментах с GCC, -std=c++11 достаточно, чтобы заставить его генерировать ошибки для вашего кода. Если вместо этого вы получаете предупреждения, это может быть связано с версией компилятора.

8

По вопросам рекламы [email protected]