Является ли 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.