Является ли gcc -Wconversion несовместимым с использованием составного присвоения (+ = и т.д.) С целыми типами короче int?
gcc имеет полезный флаг -Wconversion
, который выдает предупреждение, когда подразумевается переход от более широкого к более узкому типу, потенциально теряющий информацию. К сожалению, он имеет следующее... бесполезное... поведение.
Рассмотрим эту программу:
int main(void) {
short x = 1;
x = x+x;
return 0;
}
Компиляция с помощью -Wconversion производит
nonsense.c: In function 'main':
nonsense.c:3:8: warning: conversion to 'short int' from 'int' may alter its value [-Wconversion]
что справедливо; на большинстве платформ это будет делать то, чего вы не ожидаете, если это произойдет, если x==0x8000
. (Фактический механизм, с помощью которого вы получаете это предупреждение: операнды до +
подчиняются "обычным арифметическим преобразованиям", что расширяет их до int, поэтому результат также имеет тип int, а затем возврат к x
неявное преобразование от более широкого к более узкому типу.) Но предположим, что вы ожидаете и намерены это поведение. (Вы имитируете 16-разрядный процессор или 16-разрядный регистр сдвига или знаете диапазон возможных значений x
в этом коде, и он никогда не будет переполняться.) Вы можете сказать компилятору, вставив явное выражение:
int main(void) {
short x = 1;
x = (short)(x+x);
return 0;
}
и тогда он не будет жаловаться.
До сих пор так хорошо. Но если назначение, вызывающее проблему, является сложным назначением - +=
, *=
, <<=
и т.д. - тогда, насколько я вижу, нет способа избавиться от этого предупреждения, потому что есть 't любая точка в коде, в которую вы могли бы вставить явный приведение.
Это означает, например, что вы не можете иметь все
- -Wconversion в флагах компилятора верхнего уровня вашего проекта, чтобы уловить все реальные ошибки, на которые он предназначался.
- Любой экземпляр в любом месте кода сложных операторов присваивания, примененный к целым типам короче
int
.
- Без предупреждения.
который кажется печальным.
Итак, вопрос: есть ли хороший способ обойти это? Я вижу следующие способы:
- Используйте
#pragma GCC diagnostic ...
, чтобы отключить предупреждение в битах кода, которые, как известно, провоцируют его, несмотря на отсутствие ошибок. (Даунсайд: уродливый, специфичный для компилятора.)
- Разверните составные назначения в более длинные одиночные задания и вставьте явные приведения. (Даунсайд: супер-уродливый.)
- Отключить
-Wconversion
. (Даунсайд: это предупреждение полезно в других местах.)
- Положите предупреждения. (Даунсайд: Несовместим с использованием
-Werror
; в любом случае, это хорошая практика, чтобы ваш код не компилировался без каких-либо предупреждений, потому что разница между "никакими предупреждениями" и "некоторыми предупреждениями" легче определить, чем разница между "некоторыми предупреждения" и "еще несколько предупреждений".)
Все это кажется неудовлетворительным, и мне интересно, есть ли что-то умнее, что мне не хватает.
(Примечание: конечно, я соглашусь "нет, что это" ответ, если это правда.)
Итак, похоже, что ответ заключается в том, что это действительно намеченное поведение, и нет способа остановить его намного лучше, чем перечисленные выше. (Mark B заметил, что вы можете написать функцию, которая делает то же самое, что и +=
, но с явным литьем. Ecatmur предложил определить новый тип с функциями operator+=
, чтобы делать явное кастинг.)
Я принимаю ответ Марка Б; фактический ответ на вопрос в названии просто "да": -).
Ответы
Ответ 1
Я предполагаю, что логика предупреждения в этом случае состоит в том, что она функционально такая же, как операция, выполняемая сложным оператором. Сложный синтаксис оператора должен делать очень очевидную сложную операцию, и в этом случае вы хотите явно сказать, что точно знаете, что делаете. Когда вы хотите, чтобы было ясно, что намерение существует, явно указывается код, который явно явно выигрывает: вы пишете его только один раз, но более длинная версия будет полностью информировать будущих сопровождающих о ваших намерениях.
Я бы предложил просто отменить операцию и описать актерский состав. Ваши сторонники действительно не возражают, и я сомневаюсь, что они подумают, что это уродливо.
В качестве альтернативы создайте функцию scalar_mult(short&, short)
, чтобы скрыть подачу. Затем вы показываете явное намерение и избегаете приведения в каждом выражении.
Наконец, вы можете просто избегать более коротких типов, избегая предупреждения.
Ответ 2
Использование пользовательского типа в RHS будет работать:
struct cast_result {
int t;
};
char &operator+=(char &l, cast_result r) { return l = static_cast<char>(l + r.t); }
int main() {
char c = 'a';
c += cast_result{2};
}
Вы могли бы немного отбросить это с помощью пользовательских литералов и т.д., но это все еще довольно уродливо.
Я думаю, справедливо спросить, почему вам нужно делать арифметику по узким типам. В конструкции C обычно предполагается, что узкие типы являются или могут быть менее эффективными, чем int
.