Сужающие преобразования в C++0x. Это только я, или это звучит как прорыв?
C++0x сделает следующий код и подобный код плохо сформированным, потому что он требует так называемого сужающего преобразования double
к int
,
int a[] = { 1.0 };
Мне интересно, используется ли этот тип инициализации в коде реального мира. Сколько кода будет нарушено этим изменением? Много ли усилий для того, чтобы исправить это в вашем коде, если ваш код вообще затронут?
Для справки см. 8.5.4/6 n3225
Сужающее преобразование - это неявное преобразование
- от типа с плавающей точкой к целочисленному типу, или
- от long double до double или float, или от double до float, за исключением случаев, когда источником является константное выражение, а фактическое значение после преобразования находится в диапазоне значений, которые могут быть представлены (даже если они не могут быть представлены точно), или
- от целочисленного типа или типа перечисления с незаданной областью до типа с плавающей точкой, за исключением случаев, когда источником является константное выражение, а фактическое значение после преобразования будет соответствовать целевому типу и будет создавать исходное значение при преобразовании обратно в исходный тип, или
- от целочисленного типа или типа перечисления с незаданной областью до целочисленного типа, который не может представлять все значения исходного типа, кроме случаев, когда источником является константное выражение, а фактическое значение после преобразования будет соответствовать целевому типу и будет создавать исходное значение, когда преобразован обратно в исходный тип.
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)};
}
Код находился во внешней библиотеке, только в двух файлах. Я не думаю, что критические изменения повлияют на код. Новички могут запутаться, хотя.
Попробуйте добавить -Wno-сужение к вашим CFLAGS, например:
CFLAGS += -std=c++0x -Wno-narrowing
Я был бы удивлен и разочарован в себе, узнав, что любой код C++, который я написал за последние 12 лет, имел такую проблему. Но большинство компиляторов всегда выдавали предупреждения о любых "сужениях" во время компиляции, если я что-то не упустил.
Это также сужающие конверсии?
unsigned short b[] = { -1, INT_MAX };
Если так, то я думаю, что они могут появиться немного чаще, чем ваш пример с плавающим типом на целочисленный.
Практический пример, с которым я столкнулся:
float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};
Числовой литерал неявно double
что вызывает продвижение.
Я не удивлюсь, если кого-нибудь поймает что-то вроде:
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?
Если предположить, что ошибки нет, предположительно, исправление всегда заключается в явном преобразовании. Если вы не делаете что-то странное с макросами, я думаю, что инициализатор массива появляется только близко к типу массива или, по крайней мере, к чему-то, представляющему тип, который может зависеть от параметра шаблона. Так что актерский состав должен быть легким, если многословным.
Это было действительно серьезное изменение, поскольку реальный опыт использования этой функции показал, что во многих случаях 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 }
Сужающиеся ошибки преобразования плохо взаимодействуют с неявными целочисленными правилами продвижения.
У меня была ошибка с кодом, который выглядел как
struct char_t {
char a;
}
void function(char c, char d) {
char_t a = { c+d };
}
Что приводит к сужающей ошибке преобразования (которая является правильной в соответствии со стандартом). Причина в том, что c
а также d
неявно получить повышение до int
и в результате int
не допускается сужение назад к символу в списке инициализатора.
Ото
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) или сужает ошибки преобразования (что, возможно, хорошо).
Похоже, что GCC-4.7 больше не выдает ошибки для сужения конверсий, а вместо этого выдает предупреждения.