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

85

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

int a[] = { 1.0 };

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


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

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

  • от типа с плавающей запятой к целочисленному типу или
  • от long double до double или float, или от double до float, за исключением случаев, когда источник является постоянным выражением, а фактическое значение после преобразования находится в пределах диапазона значений, которые могут быть представлены (даже если оно не может быть представлено точно), или
  • из целочисленного типа или типа перечисления без области действия в тип с плавающей точкой, за исключением случаев, когда источник является постоянным выражением, а фактическое значение после преобразования будет соответствовать целевому типу и даст исходное значение при преобразовании обратно в исходный тип, или
  • из целочисленного типа или типа перечисления без области действия в целочисленный тип, который не может представлять все значения исходного типа, за исключением случаев, когда источник является постоянным выражением, а фактическое значение после преобразования будет соответствовать целевому типу и даст исходное значение, когда преобразован обратно в исходный тип.
Йоханнес Шауб - litb
источник
1
Предполагая, что это действительно только для инициализации встроенных типов, я не вижу, как это повредит. Конечно, это может привести к поломке кода. Но это должно быть легко исправить.
Johan Kotlinski
1
@John Dibling: Нет, инициализация не является неправильной, если значение может быть точно представлено целевым типом. (И 0уже в intлюбом случае.)
aschepler
2
@Nim: обратите внимание, что это неправильно сформировано только в {инициализаторах фигурных скобок }, и единственное устаревшее их использование - для массивов и структур POD. Кроме того, если в существующем коде есть явные приведения, которые им принадлежат, он не сломается.
aschepler
4
@j_random_hacker, как говорится в рабочем документе, int a = 1.0;все еще в силе.
Йоханнес Шауб - лит
1
@litb: Спасибо. На самом деле я нахожу это понятным, но разочаровывающим - ИМХО, было бы намного лучше потребовать явный синтаксис для всех сужающих преобразований с самого начала C ++.
j_random_hacker

Ответы:

41

Я столкнулся с этим критическим изменением, когда использовал 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)};
}

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

Timothy003
источник
9

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

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

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

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

Aschepler
источник
1
Я не понимаю, почему вы говорите, что это не редкость, которую можно найти в коде. Какова логика между использованием -1 или INT_MAX вместо USHRT_MAX? В конце 2010 года USHRT_MAX не был на подъемах?
7

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

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?

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

Стив Джессоп
источник
8
«если я знаю, что у меня есть ровно два значения, зачем помещать их в массивы» - например, потому что этого требует API, такой как OpenGL.
Георг Фриче
7

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

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

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

Джед
источник
1
так что сделайте это floatписьменно 0.5f. ;)
underscore_d
1
@underscore_d Не работает, если floatбыл параметр typedef или шаблона (по крайней мере, без потери точности), но дело в том, что написанный код работал с правильной семантикой и стал ошибкой с C ++ 11. Т.е. определение «критического изменения».
Джед
5

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

CFLAGS += -std=c++0x -Wno-narrowing
Куку Индраяна
источник
или CPPFLAGS в случае компиляторов C ++ (конечно, это зависит от вашей системы сборки или вашего Makefile)
Mikolasan
4

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

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

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) или сужение ошибок преобразования (что, возможно, хорошо).

Гюнтер Пьез
источник
Я наткнулся на такую ​​досадную чушь: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- сужение преобразования внутри {}. В самом деле? Итак, оператор & также неявно преобразует символы без знака в int? Ну, мне все равно, результат по-прежнему гарантированно будет беззнаковым символом, argh.
Карло Вуд
акции " неявного целочисленного преобразования "?
curiousguy
2

Это было действительно критическое изменение, поскольку реальный опыт использования этой функции показал, что 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}

Шафик Ягмур
источник
1

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

кику
источник