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


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

int a[] = { 1.0 };

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


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

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

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

7 ответов:

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

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

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

это также сужающие преобразования?

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

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

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

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?

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

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

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

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

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

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

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

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

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

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