Сужение преобразования в bool в инициализации списка - странное поведение
Рассмотрим этот фрагмент кода С++ 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 С++ 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
скомпилируйте этот код с без ошибок и без предупреждений. Запуск программы выводит a 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 вообще не производит никакой диагностики и компилирует код.
Есть несколько вариантов поведения, которые мы можем получить, если мы передадим константу непосредственно конструктору, некоторые странные, некоторые ожидаются, но я не буду перечислять их здесь - этот вопрос становится слишком длинным, как есть.
Я бы сказал, что это один из редких случаев, когда VС++ прав, а Clang и GCC ошибочны, когда дело доходит до стандартного соответствия, но, учитывая соответствующие записи треков этих компиляторов, я все еще очень сомневаюсь это.
Что думают эксперты?
Стандартные ссылки (цитаты из окончательного стандартного документа для С++ 11, ISO/IEC 14882-2011):
В пункте 8.5.4 [dcl.init.list] в пункте 3:
- В противном случае, если T - тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены и лучший выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если сужение конверсии (см. ниже) требуется для преобразования любого из аргументов, программа плохо сформирована.
В том же разделе, в пункте 7, мы имеем:
Сужение преобразования является неявным преобразованием - от типа с плавающей точкой до целочисленного типа, или
- от длинного двойного до двойного или плавающего или от двойного до плавающего, кроме случаев, когда источник является константой выражение и фактическое значение после преобразования находятся в диапазоне значений, которые могут быть представлены (даже если он не может быть представлен точно), или - от целочисленного типа или неперечисленного типа перечисления до типа с плавающей точкой, за исключением случаев, когда источник является постоянным выражением, а фактическое значение после преобразования будет соответствовать типу цели и будет произвести исходное значение, если оно будет возвращено к исходному типу, или
- от целочисленного типа или неперечисленного типа перечисления до целочисленного типа, который не может представлять все значения исходного типа, за исключением случаев, когда источником является постоянное выражение и фактическое значение после преобразование будет вписываться в целевой тип и будет выдавать исходное значение при преобразовании обратно в оригинальный тип.
[Примечание. Как указано выше, такие преобразования не допускаются на верхнем уровне в инициализации списка.-End примечание]
В пункте 3.9.1 [basic.fundamental] в пункте 7 мы имеем:
Типы bool, char, char16_t, char32_t, wchar_t и целые типы с подписью и без знака являются коллективными называемых интегральными типами .48 Синоним интегрального типа является целым типом.
(Я начинал все расспрашивать на этом этапе...)
Ответы
Ответ 1
Это просто похоже на ошибку, если мы попробуем следующее:
bool b {3} ;
оба gcc
и clang
выдают диагностику, например gcc
говорит:
предупреждение: сужение преобразования "3" из "int" в "bool" внутри {} [-Wnarrowing]
Это описано в проекте С++ 11 в разделе 8.5.4
Параметр 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 дает сужение ошибки для кода OPs, что свидетельствует о том, что это действительно должно привести к диагностике.
Обновить
Я отправил gcc и clang ошибка отчет.
Отчет был изменен как фиксированный:
Исправлено в r229792.