Код теста:
struct A
{
uint32_t lo : 16;
uint32_t hi : 16;
};
int main()
{
A a{};
a.lo = 0xFFFF;
auto b = a.lo << 16;
cout << b << endl;
return 0;
}
Вывод: -65536
, а тип b
— int
, но не uint32_t
.
Я обнаружил, что uint16_t
и uint8_t
также станут подписанными int после оператора сдвига, и аналогичный вопрос был в C#
, который пришел к выводу, что результат станет подписанным, когда операнд <32 бит.
Почему операции сдвига всегда приводят к знаковому целому, когда операнд <32 бит
Но тип a.lo
явно uint32_t
, что можно проверить по decltype(a.lo)
, так как же это объяснить?
Просто для записи: сдвиг целого числа определяется только для смещений, меньших его ширины. Вы сдвигаете 16-битное значение на 16 бит. Если бы это был простой uint16_t
, я бы сказал, что это UB, но, честно говоря, я не уверен в битовых полях. Однако вы можете изменить это в своем коде, чтобы не отвлекаться от фактического вопроса.
@UlrichEckhardt - это 16-битное значение, но сдвинутый операнд не является 16-битным целым числом. Это продвигается.
Кстати, MSVC этого не делает.
@xxhxx Если бы у вас было `a.lo = 0xF000; auto b = a.lo >> 1;ˋ что вы ожидаете в результате? И типа? И если бы он был подписан, вы бы ожидали, что битовое поле будет отрицательным? Я не знаю всех правил, но у разных людей могут быть разные ожидания. Максимум одно правило может быть правильным, а может быть и не указано... Если ширина ровно 16, зачем использовать битовое поле?
@Phil1970 Phil1970 Я хочу рассматривать a.lo
как обычное целое число без знака, поэтому я объявляю его как uint32_t
.
Почему бы вам просто не использовать uint16_t
, если вам нужно обычное целое число без знака с 16 битами? Это не поможет с вашей проблемой интегрального продвижения, но это более просто, чем использование битового поля той же ширины, что и существующий примитив...
Это часть стандартной интегральной акции.
[выраж.сдвиг]
1 Операторы сдвига
<<
и>>
группируются слева направоОперанды должны быть целочисленного типа или типа перечисления без области видимости и выполняются интегральные акции. Тип результата - тип расширенный левый операнд.
[конв.пром]
5 Значение prvalue для целочисленного битового поля ([class.bit]) может быть преобразуется в значение типа
int
, еслиint
может представлять все значения битового поля; в противном случае его можно преобразовать вunsigned int
, еслиunsigned int
может представлять все значения битового поля. Если битовое поле еще больше, к нему не применяется интегральное продвижение. Если битовое поле имеет перечисляемый тип, оно рассматривается как любое другое значение этот тип в рекламных целях.
Повышение вашего левого операнда (битового поля) дает int
, и поэтому это тип всего выражения сдвига. Таким образом, b
также является int
путем вывода типа заполнителя.
Одного я до сих пор не понимаю, во время работы смены объявление uint32_t
вместо a.lo
кажется не работало, а наоборот, decltype(a.lo)
приводило к uint32_t
.
@xxhxx — decltype
следует другому набору правил. Применительно к элементу он сообщает вам его объявленный тип.
Другой вопрос, беззнаковое целое битового поля, можно ли его безопасно использовать как обычное беззнаковое целое?