Подтвердить что ты не робот

Сужение преобразования в 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 Синоним интегрального типа является целым типом.

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

4b9b3361

Ответ 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.