Сужение конверсий в С++ 0x. Это только я, или это звучит как нарушение?

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

int a[] = { 1.0 };

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


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

Сужение преобразования является неявным преобразованием

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

Ответ 1

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

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

Ответ 2

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

Являются ли они также сужением конверсий?

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

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

Ответ 3

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

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}; не должна? В любом случае я инициализирую два поплавка из двух ints, это просто, что в одном случае два поплавка являются членами агрегата.

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

struct char_t {
    char a;
}

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

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

Ото

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

Ответ 6

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

CFLAGS += -std=c++0x -Wno-narrowing

Ответ 7

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

Ответ 8

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

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

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