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

Я узнал о инициализаторе, разделенном фигурными скобками, в языке программирования 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

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

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

2 ответа

Это неправильно и должно быть диагностикой, однако это может быть либо предупреждение (которое вы получили), либо ошибка. 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++, эти требования часто легче понять, если они сформулированы как требования к программам, частям программ или выполнению программ. Такие требования имеют следующее значение:

[...]

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

[...]

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

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

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

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

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

Другие вопросы по тегам