Сужающие преобразования в C ++ 0x. Это только у меня, или это похоже на переломную ситуацию?

C ++ 0x сделает следующий код и аналогичный код некорректным, поскольку он требует так называемого сужающего преобразования double в int.

int a[] = { 1.0 };

Мне интересно, часто ли такая инициализация используется в реальном коде. Сколько кода будет нарушено этим изменением? Приложено ли много усилий, чтобы исправить это в вашем коде, если он вообще затронут?


Для справки см. П. 8.5.4 / 6 в n3225.

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

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

person Johannes Schaub - litb    schedule 13.12.2010    source источник
comment
давайте надеяться, что нет, я не вижу этого типа инициализации, но они заверили, что они изо всех сил стараются не нарушать кодовую базу, C ++ 0x тем не менее имеет хорошие улучшения   -  person Saif al Harthi    schedule 14.12.2010
comment
@litb: Это тоже будет некорректно, или просто когда будет происходить преобразование? int a[10] = {0};   -  person John Dibling    schedule 14.12.2010
comment
Предполагая, что это действительно только для инициализации встроенных типов, я не вижу, как это повредит. Конечно, это может сломать какой-то код. Но это должно быть легко исправить.   -  person Johan Kotlinski    schedule 14.12.2010
comment
Я тоже надеюсь, что нет, есть много устаревшего кода, в котором я работаю, где этот тип преобразования выполняется случайно! Тем не менее, одна вещь для нас - использование C ++ 0x в производстве на данный момент несбыточная мечта .. :)   -  person Nim    schedule 14.12.2010
comment
@John Dibling: Нет, инициализация не является неправильной, если значение может быть точно представлено целевым типом. (И 0 в любом случае уже int.)   -  person aschepler    schedule 14.12.2010
comment
@Nim: Обратите внимание, что это неправильно сформировано только в { инициализаторах фигурных скобок }, и единственное устаревшее их использование - для массивов и структур POD. Кроме того, если в существующем коде есть явные приведения, где они должны быть, он не сломается.   -  person aschepler    schedule 14.12.2010
comment
Фактически, согласно правилам, unsigned char x = { -1 }; станет некорректным, как и unsigned char x = { ~0 }; на машине с дополнением до двух :)   -  person Johannes Schaub - litb    schedule 14.12.2010
comment
@litb, @aschepler: Хорошо. Фух!   -  person John Dibling    schedule 14.12.2010
comment
@Johannes: Не могли бы вы также опубликовать язык, запрещающий сужение конверсии в этом контексте (или хотя бы указатель на правый раздел черновика)? Спасибо.   -  person Ben Voigt    schedule 14.12.2010
comment
Как прокомментировано ниже, я знаю, что по крайней мере большая часть кода OpenGL будет затронута (массивы вершин с плавающей запятой или координаты на основе целых чисел в базе кода и т. Д.). Думаю, есть и другие примеры, когда требуется взаимодействие с API в стиле C.   -  person Georg Fritzsche    schedule 14.12.2010
comment
@litb: Есть ли причина, по которой вы выбрали объявление массива вместо простого int a = 1.0; или int a; a = 1.0;?   -  person j_random_hacker    schedule 14.12.2010
comment
@j_random_hacker, как говорится в рабочем документе, int a = 1.0; все еще действителен.   -  person Johannes Schaub - litb    schedule 14.12.2010
comment
@litb: Спасибо. На самом деле я нахожу это понятным, но разочаровывающим - ИМХО, было бы намного лучше потребовать явный синтаксис для всех сужающих преобразований с самого начала C ++.   -  person j_random_hacker    schedule 14.12.2010


Ответы (8)


Я столкнулся с этим критическим изменением, когда использовал GCC. Компилятор напечатал ошибку для такого кода:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}

В функции void foo(const long long unsigned int&):

ошибка: сужение преобразования (((long long unsigned int)i) & 4294967295ull) с long long unsigned int на unsigned int внутри {}

ошибка: сужение преобразования (((long long unsigned int)i) >> 32) с long long unsigned int на unsigned int внутри {}

К счастью, сообщения об ошибках были простыми, и исправить было несложно:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
            static_cast<unsigned int>(i >> 32)};
}

Код находился во внешней библиотеке, всего два экземпляра в одном файле. Я не думаю, что критическое изменение сильно повлияет на код. Новички могут получить запутались, хоть.

person Timothy003    schedule 14.01.2011

Я был бы удивлен и разочарован, узнав, что любой код C ++, который я написал за последние 12 лет, имел такого рода проблемы. Но большинство компиляторов выдавали бы предупреждения о любых «сужениях» времени компиляции, если я чего-то не упускаю.

Это тоже сужает конверсию?

unsigned short b[] = { -1, INT_MAX };

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

person aschepler    schedule 13.12.2010
comment
Я не понимаю, почему вы говорите, что это не редкость, которую можно найти в коде. Какова логика между использованием -1 или INT_MAX вместо USHRT_MAX? В конце 2010 года USHRT_MAX не находился на подъеме? - person ; 20.04.2017

Практический пример, с которым я столкнулся:

float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};

Числовой литерал неявно double, что вызывает повышение.

person Jed    schedule 03.11.2012
comment
так что сделайте это float, написав 0.5f. ;) - person underscore_d; 05.10.2016
comment
@underscore_d Не работает, если float был параметром typedef или шаблона (по крайней мере, без потери точности), но дело в том, что написанный код работал с правильной семантикой и стал ошибкой с C ++ 11. То есть определение критического изменения. - person Jed; 05.10.2016

Не удивлюсь, если кого-нибудь поймают на словах вроде:

float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};

(в моей реализации последние два не дают того же результата при преобразовании обратно в int / long, следовательно, сужаются)

Однако я не помню, чтобы когда-либо писал это. Это полезно, только если приближение к пределам полезно для чего-то.

Это тоже кажется хотя бы отдаленно правдоподобным:

void some_function(int val1, int val2) {
    float asfloat[] = {val1, val2};    // not in C++0x
    double asdouble[] = {val1, val2};  // not in C++0x
    int asint[] = {val1, val2};        // OK
    // now do something with the arrays
}

но это не совсем убедительно, потому что, если я знаю, что у меня ровно два значения, зачем помещать их в массивы, а не просто float floatval1 = val1, floatval1 = val2;? Но какова мотивация, почему это должно компилироваться (и работать, при условии, что потеря точности находится в пределах приемлемой точности для программы), а float asfloat[] = {val1, val2}; - нет? В любом случае я инициализирую два числа с плавающей запятой из двух целых чисел, просто в одном случае эти два числа с плавающей запятой являются членами агрегата.

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

char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?

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

person Community    schedule 14.12.2010
comment
если я знаю, что у меня ровно два значения, зачем помещать их в массивы - например, потому что этого требует такой API, как OpenGL. - person Georg Fritzsche; 14.12.2010

Попробуйте добавить -Wno-сужение к своим CFLAGS, например:

CFLAGS += -std=c++0x -Wno-narrowing
person Kukuh Indrayana    schedule 20.05.2017
comment
или CPPFLAGS в случае компиляторов C ++ (конечно, это зависит от вашей системы сборки или вашего Makefile) - person Mikolasan; 22.09.2020
comment
Хорошее исправление для проверки нечистого кода. - person not2qubit; 16.05.2021

Ошибки сужающегося преобразования плохо взаимодействуют с неявными целочисленными правилами продвижения.

У меня была ошибка с кодом, который выглядел как

struct char_t {
    char a;
}

void function(char c, char d) {
    char_t a = { c+d };
}

Что дает сужающую ошибку преобразования (что правильно по стандарту). Причина в том, что c и d неявно повышаются до int, и результирующий int не может быть сужен до char в списке инициализаторов.

OTOH

void function(char c, char d) {
    char a = c+d;
}

конечно все еще в порядке (иначе бы весь ад вырвался на свободу). Но что удивительно, даже

template<char c, char d>
void function() {
    char_t a = { c+d };
}

в порядке и компилируется без предупреждения, если сумма c и d меньше CHAR_MAX. Я все еще думаю, что это дефект C ++ 11, но люди там думают иначе - возможно, потому, что его нелегко исправить, не избавившись от неявного целочисленного преобразования (что является пережитком прошлого, когда люди писали код как char a=b*c/d, и ожидал, что он будет работать, даже если (b * c)> CHAR_MAX) или сужение ошибок преобразования (что, возможно, хорошо).

person Gunther Piez    schedule 23.03.2012
comment
Я наткнулся на досадную чушь: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m }; ‹- сужение преобразования внутри {}. Действительно? Итак, оператор & также неявно преобразует символы без знака в int? Ну, мне все равно, результат по-прежнему гарантированно будет беззнаковым символом, argh. - person Carlo Wood; 07.12.2016
comment
неявное целочисленное преобразование рекламных акций? - person curiousguy; 28.07.2018

Это действительно было критическое изменение, поскольку реальный жизненный опыт использования этой функции показал, что gcc превратил сужение в предупреждение об ошибке во многих случаях из-за реальных жизненных проблем с переносом баз кода C ++ 03 на C ++ 11. См. этот комментарий в отчете об ошибке gcc:

Стандарт требует только, чтобы соответствующая реализация выдавала хотя бы одно диагностическое сообщение, поэтому компиляция программы с предупреждением разрешена. Как сказал Эндрю, -Werror = сужение позволяет вам сделать ошибку, если хотите.

G ++ 4.6 выдавал ошибку, но для версии 4.7 она была намеренно изменена на предупреждение, потому что многие люди (в том числе и я) обнаружили, что сужение преобразований является одной из наиболее часто встречающихся проблем при попытке скомпилировать большой C Кодовые базы ++ 03 как C ++ 11. Ранее правильно сформированный код, такой как char c [] = {i, 0}; (где i всегда будет в пределах диапазона char) вызвали ошибки, и их пришлось заменить на char c [] = {(char) i, 0}

person Shafik Yaghmour    schedule 18.12.2018
comment
См. Также: https://gcc.gnu.org/wiki/FAQ#Wnarrowing - person Amir Kirsh; 13.01.2021

Похоже, что GCC-4.7 больше не выдает ошибки для сужения конверсий, а вместо этого выдает предупреждения.

person kyku    schedule 29.05.2012