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