Сужение преобразования в bool при инициализации списка - странное поведение

Рассмотрим этот фрагмент кода C++11:

#include <iostream>

struct X
{
    X(bool arg) { std::cout << arg << '\n'; }
};

int main() 
{
    double d = 7.0;
    X x{d};
}

При инициализации x происходит сужающее преобразование из double в bool. Согласно моему пониманию стандарта, это неправильно сформированный код, и мы должны увидеть некоторую диагностику.

Visual C++ 2013 выдает ошибку:

error C2398: Element '1': conversion from 'double' to 'bool' requires a narrowing conversion

Однако и Clang 3.5.0, и GCC 4.9.1, используя следующие параметры

-Wall -Wextra -std=c++11 -pedantic 

скомпилируйте этот код без ошибок и предупреждений. Запуск программы выводит 1 (неудивительно).


Теперь давайте углубимся в странную территорию.

Меняем X(bool arg) на X(int arg) и вдруг получаем ошибку от Clang

error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]

и предупреждение от GCC

warning: narrowing conversion of 'd' from 'double' to 'int' inside { } [-Wnarrowing]

Это больше похоже на то, что я ожидал.


Теперь сохраните аргумент конструктора bool (то есть вернитесь к X(bool arg)) и измените double d = 7.0; на int d = 7;. Опять ошибка сужения от Clang, но GCC вообще не выдает никакой диагностики и компилирует код.

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


Я бы сказал, что это один из тех редких случаев, когда VC++ прав, а Clang и GCC ошибаются, когда дело доходит до соответствия стандартам, но, учитывая соответствующий послужной список этих компиляторов, я все еще очень сомневаюсь в этом.

Что думают эксперты?


Стандартные ссылки (цитаты из окончательного стандартного документа для С++ 11, ISO/IEC 14882-2011):

В 8.5.4 [dcl.init.list] пункт 3 имеем:

— В противном случае, если T — тип класса, рассматриваются конструкторы. Перечисляются применимые конструкторы, и лучший из них выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. ниже), программа некорректна.

В этом же разделе, в пункте 7, имеем:

Сужающее преобразование — это неявное преобразование
— из типа с плавающей запятой в целочисленный тип или
— из long double в double или float или из double в float, за исключением случаев, когда источником является константное выражение и фактическое значение после преобразования находится в пределах диапазона значений, которые могут быть представлены (даже если оно не может быть представлено точно), или
— из целочисленного типа или типа перечисления с незаданной областью в тип с плавающей запятой, за исключением случаев, когда источник постоянное выражение и фактическое значение после преобразования будут соответствовать целевому типу и дадут исходное значение при преобразовании обратно в исходный тип, или
— из целочисленного типа или типа перечисления с незаданной областью в целочисленный тип, который не может представлять все значения исходного типа, за исключением случаев, когда источником является постоянное выражение, а фактическое значение после преобразования соответствует целевому типу и создает исходное значение при обратном преобразовании в исходный тип.
[ Примечание. Как указано выше, такие преобразования не разрешены на верхнем уровне при инициализации списка. — примечание в конце]

В 3.9.1 [basic.fundamental] пункт 7 имеем:

Типы bool, char, char16_t, char32_t, wchar_t, а также целые типы со знаком и без знака вместе называются интегральными типами48. Синонимом целочисленного типа является целочисленный тип.

(Я начал сомневаться во всем на этом этапе...)


person bogdan    schedule 16.12.2014    source источник
comment
Эй, куда делись все комментарии? Некоторые из них содержали полезную информацию для диагностики проблемы, в частности для Clang.   -  person bogdan    schedule 17.12.2014
comment
Некоторые из этих комментариев были бы очень полезны для подачи отчетов об ошибках, я не понимаю, почему они все были удалены, возможно, спрашивая в мета Может поможет, сейчас нет времени. Вы также можете попробовать собственный флаг, но вы не знаете, через какое время он будет активирован.   -  person Shafik Yaghmour    schedule 17.12.2014
comment
@dyp не уверен, что вы это увидите, так как ваш комментарий был удален, но ваши полезные ссылки на исходный код clang были удалены, и было бы полезно вернуть их.   -  person Shafik Yaghmour    schedule 01.01.2015
comment
@ShafikYaghmour Нет, не видел. У меня нет учетных записей Bugzilla ни для GCC, ни для Clang, и получить одну из них оказалось сложнее, чем просто запросить ее (никогда не получал никаких электронных писем от системы). У меня не было больше времени следить за этим.   -  person bogdan    schedule 01.01.2015
comment
Файлы clang и gcc отчет об ошибке.   -  person Shafik Yaghmour    schedule 12.02.2015
comment
clang закрыл сообщение об ошибке как исправленное.   -  person Shafik Yaghmour    schedule 19.02.2015
comment
@ShafikYaghmour Хороший материал, спасибо, что позаботились об этом.   -  person bogdan    schedule 19.02.2015
comment
@PravasiMeet Я отправил отчеты об ошибках, они оба связаны в моем ответе ниже. Время отклика на сообщения об ошибках сильно различается, я не думаю, что мы можем многое сделать со стороны gcc.   -  person Shafik Yaghmour    schedule 09.09.2015
comment
Похоже, и gcc, и clang исправили это.   -  person Shafik Yaghmour    schedule 24.08.2018


Ответы (1)


Это просто выглядит как ошибка, если мы попробуем следующее:

bool b {3} ;

и gcc, и clang выдают диагностику, например gcc говорит:

предупреждение: сужение преобразования '3' из 'int' в 'bool' внутри { } [-Wnarrowing]

Это описано в черновике C++11. стандарт разделом 8.5.4 List-initialization параграфом 7, в котором говорится:

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

[...]

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

Это тот же абзац, который охватывает ваш пример и следующий более простой пример:

bool a {3.0} ;

который будет охвачен этим пунктом из абзаца 7, процитированного выше:

  • из типа с плавающей запятой в целочисленный тип, или

Из пункта 3 это неправильно и требует диагностики:

Список-инициализация объекта или ссылки типа T определяется следующим образом:

[...]

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

который gcc не производит диагностики, но clang выдает следующее предупреждение, хотя и не предупреждение о сужающем преобразовании, которое мы должны увидеть:

предупреждение: неявное преобразование из 'double' в 'bool' изменяет значение с 3 на true [-Wliteral-conversion]

Обратите внимание, в разделе 3.9.1 [basic.fundamental] говорится:

Типы bool, char, char16_t, char32_t, wchar_t, а также целые типы со знаком и без знака вместе называются интегральными типами.48 Синонимом целочисленного типа является целочисленный тип. .

Вы должны отправить отчет об ошибке с помощью clang и gcc.

Джонатан Уэйкли отмечает, что компилятор EDG выдает сужающую ошибку для кода OP, что является убедительным признаком того, что это действительно должно привести к диагностике.

Обновить

Я отправил gcc и clang отчет об ошибке.

отчет об ошибках clang обновлен и исправлен:

Исправлено в r229792.

отчет об ошибках gcc обновлен и исправлен:

Фиксированный.

и живой пример подтверждает это.

person Shafik Yaghmour    schedule 16.12.2014
comment
Да, предупреждение об инициализации bool с константой double подпадает под «еще несколько вариантов поведения», которые я упомянул в вопросе. По сути, в этом случае Clang по-прежнему не определяет, что это сужающее преобразование при инициализации списка, но возвращается к тому же предупреждению, которое он выдает для любой такой инициализации с постоянным значением (он выдаст такое же предупреждение, если вы замените фигурные скобки с круглыми скобками, что делает это допустимым неявным преобразованием). - person bogdan; 16.12.2014