Неявное преобразование C ++ (подписано + неподписано)

Я понимаю, что в отношении неявных преобразований, если у нас есть операнд типа без знака и операнд типа со знаком, а тип операнда без знака совпадает (или больше) с типом операнда со знаком, знаковый операнд будет преобразован без подписи.

Так:

unsigned int u = 10;
signed int s = -8;

std::cout << s + u << std::endl;

//prints 2 because it will convert `s` to `unsigned int`, now `s` has the value
//4294967288, then it will add `u` to it, which is an out-of-range value, so,
//in my machine, `4294967298 % 4294967296 = 2`

Что я не понимаю — я прочитал это, если подписанный операнд имеет больший тип, чем беззнаковый операнд:

  • если все значения в типе без знака соответствуют большему типу, то беззнаковый операнд преобразуется в тип со знаком

  • если значения в типе без знака не вписываются в больший тип, тогда операнд со знаком будет преобразован в тип без знака

так в следующем коде:

signed long long s = -8;
unsigned int u = 10;
std::cout << s + u << std::endl;

u будет преобразован в long long со знаком, потому что значения int могут уместиться в long long со знаком ??

Если это так, то в каком сценарии меньшие значения типов не будут соответствовать большему?

11

Решение

Соответствующая цитата из Стандарта:

5 выражений [expr]

10 Многие бинарные операторы, которые ожидают операнды арифметики или
Тип перечисления вызывает преобразования и приводит к типам результата в аналогичном
путь. Цель состоит в том, чтобы получить общий тип, который также является типом
результат. Эта модель называется обычными арифметическими преобразованиями,
которые определены следующим образом:

[2 пункта о равных типах или типах знака равенства опущены]

— В противном случае, если операнд с целым типом без знака имеет ранг
больше или равно рангу типа другого операнда,
операнд со знаком целочисленного типа должен быть преобразован в тип
операнд с целым типом без знака.

— В противном случае, если тип
операнд со знаком целочисленного типа может представлять все значения
типа операнда с целым типом без знака, операнд
с целым беззнаковым типом преобразуется в тип
операнд со знаком целочисленного типа.

— В противном случае оба операнда должны быть
преобразуется в целое число без знака, соответствующее типу
операнд со знаком целочисленного типа.

Давайте рассмотрим следующие 3 примера для каждого из 3 пунктов выше в системе, где sizeof(int) < sizeof(long) == sizeof(long long) (легко адаптируется к другим случаям)

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;

signed long int s2 = -4;
unsigned int u2 = 2;

signed long long int s3 = -4;
unsigned long int u3 = 2;

int main()
{
std::cout << (s1 + u1) << "\n"; // 4294967294
std::cout << (s2 + u2) << "\n"; // -2
std::cout << (s3 + u3) << "\n"; // 18446744073709551614
}

Живой пример с выходом.

Первый пункт: типы одинакового ранга, поэтому signed int операнд преобразуется в unsigned int, Это влечет за собой преобразование значения, которое (с использованием дополнения до двух) дает напечатанное значение.

Второе предложение: тип со знаком имеет более высокий ранг и (на этой платформе!) Может представлять все значения типа без знака, поэтому беззнаковый операнд преобразуется в тип со знаком, и вы получаете -2

Третье предложение: подписанный тип снова имеет более высокий ранг, но (на этой платформе!) Не может представлять все значения беззнакового типа, поэтому оба операнда преобразуются в unsigned long longи после преобразования значения в подписанном операнде вы получите напечатанное значение.

Обратите внимание, что когда беззнаковый операнд будет достаточно большим (например, 6 в этих примерах), тогда конечный результат даст 2 для всех 3 примеров из-за переполнения целого числа без знака.

(Добавлено) Обратите внимание, что вы получаете еще более неожиданные результаты при сравнении этих типов. Рассмотрим приведенный выше пример 1 с <:

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;
int main()
{
std::cout << (s1 < u1 ? "s1 < u1" : "s1 !< u1") << "\n";  // "s1 !< u1"std::cout << (-4 < 2u ? "-4 < 2u" : "-4 !< 2u") << "\n";  // "-4 !< 2u"}

поскольку 2u сделан unsigned явно u Суффикс применяются те же правила. И результат, вероятно, не то, что вы ожидаете при сравнении -4 < 2 при написании в C ++ -4 < 2u

17

Другие решения

signed int не вписывается в unsigned long long, Так что у вас будет это преобразование:
signed int -> unsigned long long,

2

Обратите внимание, что стандарт C ++ 11 здесь не говорит о больших или меньших типах, он говорит о типах с более низким или более высоким рангом.

Рассмотрим случай long int а также unsigned int где оба 32-битные. long int имеет больший ранг, чем unsigned int, но с тех пор long int а также unsigned int оба 32-битные, long int не может представлять все значения unsigned int,

Поэтому мы переходим к последнему случаю (C ++ 11: 5.6p9):

  • В противном случае оба операнда должны быть преобразованы в целочисленный тип без знака, соответствующий
    тип операнда со знаком целочисленного типа.

Это означает, что оба long int и unsigned int будет преобразован в unsigned long int,

0