Неявное преобразование С++ (Signed + Unsigned)

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

Итак:

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 long, потому что значения int могут вписываться в долгое долгое знаком

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

Ответы

Ответ 1

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

5 Выражения [expr]

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

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

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

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

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

Рассмотрим следующие 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 при записи на С++ -4 < 2u...

Ответ 2

signed int не вписывается в unsigned long long. Таким образом, вы получите следующее преобразование: signed intunsigned long long.

Ответ 3

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

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

Поэтому мы попадаем в последний случай (С++ 11: 5.6p9):

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

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