Сужающие преобразования в C ++ 0x. Это только я, или это звучит как прорыв? - PullRequest
81 голосов
/ 14 декабря 2010

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

int a[] = { 1.0 };

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


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

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

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

Ответы [ 8 ]

41 голосов
/ 14 января 2011

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

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

9 голосов
/ 14 декабря 2010

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

Являются ли они также сужающими преобразованиями?думаю, что они могут подходить немного чаще, чем ваш пример с плавающим типом к целочисленному типу.

7 голосов
/ 14 декабря 2010

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

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

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

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

Это кажется, по крайней мере, слишком правдоподобным:

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?

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

5 голосов
/ 03 ноября 2012

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

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

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

4 голосов
/ 20 мая 2017

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

CFLAGS += -std=c++0x -Wno-narrowing
4 голосов
/ 23 марта 2012

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

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

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

1 голос
/ 18 декабря 2018

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

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

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

1 голос
/ 29 мая 2012

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

...