Звуження конверсій у C ++ 0x. Це лише я, чи це звучить як надзвичайна зміна?


85

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

int a[] = { 1.0 };

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


Довідково див. 8.5.4 / 6 n3225

Перетворення звуження - це неявне перетворення

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

1
Припускаючи, що це дійсно лише для ініціалізації вбудованих типів, я не можу зрозуміти, як це може зашкодити. Звичайно, це може зламати якийсь код. Але це має бути легко виправити.
Йохан Котлінський

1
@John Dibling: Ні, ініціалізація не є неправильно сформованою, коли значення може бути точно представлене цільовим типом. (І 0вже в intбудь-якому випадку.)
aschepler

2
@Nim: Зверніть увагу, що це лише неправильно сформовано в {фігурних фігурних дужках }, і єдиним застарілим використанням цих даних є масиви та структури POD. Крім того, якщо в існуючому коді є явні заливки, де вони належать, він не зламається.
aschepler

4
@j_random_hacker, як сказано в робочому документі, int a = 1.0;все ще діє.
Йоханнес Шауб - Litb

1
@litb: Дякую. Насправді я вважаю це зрозумілим, але невтішним - IMHO було б набагато краще вимагати явного синтаксису для всіх звужувальних перетворень з самого початку 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)};
}

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


9

Я був би здивований і розчарований у собі, дізнавшись, що будь-який код С ++, який я писав за останні 12 років, мав подібні проблеми. Але більшість компіляторів весь час видавали попередження про будь-які "звуження" часу компіляції, якщо я чогось не пропустив.

Це також звуження конверсій?

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

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


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.
Georg Fritzsche

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заборонено звужувати назад до символу в списку ініціалізатора.

ОТОХ

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? Ну, мені все одно, результатом все одно буде гарантія без підпису, арг.
Карло Вуд

акції " неявної цілочисельної конверсії "?
curiousguy

2

Це справді було надзвичайною зміною, оскільки досвід реальної життя з цією функцією показав, що 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

Схоже, GCC-4.7 більше не видає помилок для звуження перетворень, а натомість попередження.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.