Рассмотрим этот фрагмент кода 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. Синонимом целочисленного типа является целочисленный тип.
(Я начал сомневаться во всем на этом этапе...)