Правильный современный способ преобразования двух uint8_t в int16_t

Каков правильный современный способ C++ для преобразования двух uint8t в один int16_t, который удовлетворит лязг-аккуратность?

a[0] << 8 | a[1]

Если a[0] >= 128, я хочу, чтобы результат зацикливался, что приводило к отрицательному значению.

Сообщение Clang-Tidy

Use of a signed integer operand with a binary bitwise operator

person vasily    schedule 02.08.2019    source источник
comment
Не знаю, что удовлетворит clang-tidy, но вам нужно привести a[0] к int16_t до сдвига влево, иначе вы в конечном итоге сдвинете uint8_t влево на 8 бит и потеряете биты.   -  person Jeremy Friesner    schedule 02.08.2019
comment
@JeremyFriesner Clang-Tidy говорит Использование целочисленного операнда со знаком с бинарным побитовым оператором   -  person vasily    schedule 02.08.2019
comment
@JeremyFriesner - я могу ошибаться, но копия стандарта C ++ говорит: The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand. Это подразумевает для меня, что a[0] << 8 не теряет биты, а вместо этого повышается до чего-то большего. Здесь мне нужен языковой юрист для перевода, но быстрый тест компиляции показывает, что это так. Но это может быть неопределенное поведение, соответствующее обычной реализации. Кто-нибудь, помогите мне здесь.   -  person selbie    schedule 02.08.2019
comment
@selbie А до чего будет повышен uint8_t? Как это согласуется с сообщением Clang-Tidy, упомянутым в комментариях?   -  person JaMiT    schedule 02.08.2019
comment
@vasily Мне кажется, что точное сообщение слишком важно для объяснения вопроса, чтобы оставлять его в комментариях...   -  person JaMiT    schedule 02.08.2019
comment
Застенчиво предлагает... static_cast()?   -  person    schedule 02.08.2019
comment
См. раздел Числовые рекламные акции — cppreference.com (середина страницы)   -  person David C. Rankin    schedule 02.08.2019
comment
Каков ожидаемый результат, если a[0] >= 128? Отрицательное число?   -  person L. F.    schedule 02.08.2019
comment
a[0] * 256 + a[1] работает так же хорошо и генерирует идентичный исполняемый код с современными компиляторами.   -  person n. 1.8e9-where's-my-share m.    schedule 02.08.2019
comment
@ Л.Ф. да, в даташите так и выглядит.   -  person vasily    schedule 02.08.2019
comment
Кстати, @selbie, похоже, ты прав. Короткие типы, похоже, продвигаются до целых.   -  person    schedule 02.08.2019
comment
Кроме того, полезно, но я бы не назвал дубликат. Кажется, они находятся в несколько иной ситуации.   -  person    schedule 02.08.2019


Ответы (1)


Вот один из способов преобразования в uint16_t (мы поговорим о int16_t позже)

constexpr std::uint16_t combine(std::uint8_t a, std::uint8_t b)
{
    return static_cast<unsigned>(a) << 8 | static_cast<unsigned>(b);
}

(демонстрация на C++11)

Один из способов обеспечить переносимость — сначала преобразовать значение uint8_t в unsigned int, чтобы обеспечить предсказуемое целочисленное продвижение. Значение сохраняется независимо от типа с псевдонимом uint8_t, потому что unsigned int гарантированно способен содержать все неотрицательные целые числа ниже 256, а правила преобразования целых чисел без знака гарантируют сохранение значения. Тогда операции гарантированно будут работать с unsigned int вместо целочисленного типа со знаком.

Вопрос: зачем переходить на unsigned? Вы смешиваете типы. Почему бы не преобразовать в uint16_t напрямую?

Обратите внимание, что использование вместо этого static_cast<uint16_t>(a) не является не переносимым решением, поскольку uint16_t все еще может быть повышено до int в зависимости от среды (например, когда uint16_t равно unsigned short, а int равно 32 битам). Преобразование в unsigned дает нам полный контроль над правилами продвижения целых чисел.


Теперь OP хочет преобразовать это число в int16_t, в результате чего получится отрицательное число, если a >= 128. Вы можете быть уверены, что используете систему с дополнением до двух, если вы действительно собираетесь это сделать, поэтому простого static_cast может быть достаточно. В противном случае можно использовать отдельную проверку. Опять же, немного странно.

person L. F.    schedule 02.08.2019
comment
@Chipster Потому что uint16_t может быть повышен до int. Кроме того, хороший вопрос. - person L. F.; 02.08.2019
comment
Извините, только что понял, что на самом деле это int16_t, а не uint16_t. В любом случае, разве ОП не нуждается в этом в int16_t? Зачем продвигать его в int? Должно быть, я упускаю что-то действительно очевидное. - person ; 02.08.2019
comment
Я не понимаю, почему uint16_t может быть повышен до int. Этот тип гарантированно будет беззнаковым, если я не ошибаюсь. - person Fareanor; 02.08.2019
comment
@Fareanor Предположим, что uint16_t равно unsigned short, а int равно 32 битам. Затем согласно N3337 [conv.prom]/1, unsigned short повышается до int. - person L. F.; 02.08.2019
comment
Ах хорошо, я этого не знал. Но в любом случае очень странно так произвольно менять тип объекта. У вас есть идея о причине разрушения типов, когда они являются prvalues? Для меня это нонсенс... - person Fareanor; 02.08.2019
comment
@Fareanor C был разработан для неявного и незаметного изменения целочисленных типов операндов, используемых в выражениях. Существует несколько случаев, когда язык заставляет компилятор либо изменить операнды на более крупный тип, либо изменить их знак. Обоснование этого состоит в том, чтобы предотвратить случайные переполнения во время арифметических операций, а также позволить операндам с разным знаком сосуществовать в одном и том же выражении... - person L. F.; 02.08.2019
comment
... К сожалению, правила неявного продвижения типов приносят гораздо больше вреда, чем пользы, вплоть до того, что они могут быть одним из самых больших недостатков языка C. Эти правила часто даже не известны среднестатистическому программисту на C, и поэтому они вызывают всевозможные очень тонкие ошибки. (stackoverflow.com/a/46073296) C++ в значительной степени наследует эти вещи от C. - person L. F.; 02.08.2019
comment
А, спасибо. Я не знал о существовании интегральных акций. Но, чтобы быть уверенным, если вы объявили uint16_t и присвоили ему результат static_cast<uint16_t>, поскольку получателем является uint16_t, правильно ли я скажу, что этого достаточно, чтобы избежать интегрального продвижения? Если нет, то очень странно, что даже в C или C++ мы теряем контроль над тем, с чем работаем. - person Fareanor; 02.08.2019
comment
Давайте продолжим обсуждение в чате. - person L. F.; 02.08.2019
comment
Я не могу присоединиться, мой доступ к сети ограничен. Но спасибо за пояснения :) - person Fareanor; 02.08.2019
comment
@Fareanor О, правда. Да, интегральные повышения выполняются только тогда, когда вы занимаетесь арифметикой. Передача значений не приводит к интегральному продвижению. - person L. F.; 02.08.2019
comment
Спасибо, тогда я успокоился :) Извините за лишние комментарии. - person Fareanor; 02.08.2019