Почему беззнаковое целое битового поля становится подписанным целым числом после операции сдвига в С++?

Код теста:

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, а тип bint, но не uint32_t.

Я обнаружил, что uint16_t и uint8_t также станут подписанными int после оператора сдвига, и аналогичный вопрос был в C#, который пришел к выводу, что результат станет подписанным, когда операнд <32 бит. Почему операции сдвига всегда приводят к знаковому целому, когда операнд <32 бит

Но тип a.lo явно uint32_t, что можно проверить по decltype(a.lo), так как же это объяснить?

Другой вопрос, беззнаковое целое битового поля, можно ли его безопасно использовать как обычное беззнаковое целое?

xxhxx 15.12.2020 13:52

Просто для записи: сдвиг целого числа определяется только для смещений, меньших его ширины. Вы сдвигаете 16-битное значение на 16 бит. Если бы это был простой uint16_t, я бы сказал, что это UB, но, честно говоря, я не уверен в битовых полях. Однако вы можете изменить это в своем коде, чтобы не отвлекаться от фактического вопроса.

Ulrich Eckhardt 15.12.2020 13:58

@UlrichEckhardt - это 16-битное значение, но сдвинутый операнд не является 16-битным целым числом. Это продвигается.

StoryTeller - Unslander Monica 15.12.2020 14:03

Кстати, MSVC этого не делает.

user1143634 15.12.2020 14:07

@xxhxx Если бы у вас было `a.lo = 0xF000; auto b = a.lo >> 1;ˋ что вы ожидаете в результате? И типа? И если бы он был подписан, вы бы ожидали, что битовое поле будет отрицательным? Я не знаю всех правил, но у разных людей могут быть разные ожидания. Максимум одно правило может быть правильным, а может быть и не указано... Если ширина ровно 16, зачем использовать битовое поле?

Phil1970 15.12.2020 14:10

@Phil1970 Phil1970 Я хочу рассматривать a.lo как обычное целое число без знака, поэтому я объявляю его как uint32_t.

xxhxx 15.12.2020 14:14

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

Useless 15.12.2020 15:16
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
7
227
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это часть стандартной интегральной акции.

[выраж.сдвиг]

1 Операторы сдвига << и >> группируются слева направо

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

[конв.пром]

5 Значение prvalue для целочисленного битового поля ([class.bit]) может быть преобразуется в значение типа int, если int может представлять все значения битового поля; в противном случае его можно преобразовать в unsigned int, если unsigned int может представлять все значения битового поля. Если битовое поле еще больше, к нему не применяется интегральное продвижение. Если битовое поле имеет перечисляемый тип, оно рассматривается как любое другое значение этот тип в рекламных целях.

Повышение вашего левого операнда (битового поля) дает int, и поэтому это тип всего выражения сдвига. Таким образом, b также является int путем вывода типа заполнителя.

Одного я до сих пор не понимаю, во время работы смены объявление uint32_t вместо a.lo кажется не работало, а наоборот, decltype(a.lo) приводило к uint32_t.

xxhxx 15.12.2020 14:20

@xxhxx — decltype следует другому набору правил. Применительно к элементу он сообщает вам его объявленный тип.

StoryTeller - Unslander Monica 15.12.2020 14:25

Другие вопросы по теме

Обрабатывает ли sort -n связи предсказуемо, когда параметр --stable НЕ указан? Если да, то как?
Гарантированно ли сохраняется порядок записи в отдельные члены изменчивой структуры?
Decltype на переменную ссылочного типа с фигурными скобками
ISO C++ говорит, что они неоднозначны, хотя наихудшее преобразование для первого лучше, чем наихудшее преобразование для второго
Проблема с примером после [conv.lval#2.2]
Рассматривается ли преобразование идентификатора типа указателя как преобразование квалификации при ранжировании
Гарантируется ли стандартом порядок инициализации векторных элементов?
Неполные типы в возвращаемом типе и параметрах функции объявлены, но не определены
C - поведение преобразования между двумя указателями
Почему выражение не является «основным константным выражением», если его вычисление требует вычисления ссылки?