2

On my laptop, running the following code:

#include <iostream>
using namespace std;

int main()
{
    char a;
    cout << sizeof(~a) << endl;
}

prints 4.

I expected the result of ~a to be a char, but apparently, it is an int.

Why is that?

Han Qiu
  • 631
  • 11
  • 17
  • 1
    While this is probably a dupe (looking for one), I don't get the downvote. This does not look trivial to search for for me if one does not know about the integral promotion rules. – Baum mit Augen May 05 '16 at 16:34
  • 2
    [This](https://stackoverflow.com/questions/30473958/what-is-going-on-with-bitwise-operators-and-integer-promotion) is not bad for a start. OP: You think that's good enough? – Baum mit Augen May 05 '16 at 16:36
  • Oops, didn't notice the `sizeof`. – Barmar May 05 '16 at 16:45
  • [Implicit type conversion rules in C++ operators](http://stackoverflow.com/q/5563000/995714) – phuclv May 05 '16 at 17:00

2 Answers2

5

~ is an arithemtic operator (bitwise NOT), and a is being promoted from signed char to int (and in many implementations sizeof(int) == 4). See below for an explanation:

http://en.cppreference.com/w/cpp/language/implicit_conversion#integral_promotion

Prvalues of small integral types (such as char) may be converted to prvalues of larger integral types (such as int). In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable. This conversion always preserves the value.

MuertoExcobito
  • 9,741
  • 2
  • 37
  • 78
  • Thank you for your answer! I think I've got the idea. But I'm wondering why it's implemented this way in c++. – Han Qiu May 05 '16 at 16:50
  • 1
    @HanQiu Because C does it too and C++ was supposed to be widely compatible. Now why C does it I don't know. Maybe some *"`int` is the fastest integer type reason"*. – Baum mit Augen May 05 '16 at 16:52
  • @BaummitAugen Any proof about `int` is fastest? And most of us use 64bits machine nowadays, is 32bits `int` still fastest? – Han Qiu May 05 '16 at 17:08
  • 1
    @HanQiu `int` was supposed to be the natural integer type on the machine back when C was created. Back in the 70s, 64bit machines probably were of little concern. ;) – Baum mit Augen May 05 '16 at 17:12
  • Also in assembly bitwise and arithmetic operations in general involve at least one register, and when you move the value to the register you have to promote it (unless you're compiling for an 8-bit processor). So when you have multiple operations it makes sense to do the promotion as the first step, for an efficient machine code result. – Fabio Ceconello May 05 '16 at 17:15
3

The standard says (§[expr.primary]/10):

The operand of ~ shall have integral or unscoped enumeration type; the result is the one’s complement of its operand. Integral promotions are performed. The type of the result is the type of the promoted operand.

"Integral promotions" means (§[conv.prom]/1):

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

In your case, a has type char, which has a conversion rank less than the rank of int1, so it's being promoted to either int or unsigned int, both of which have the same size (apparently 4 in your implementation).

As to why things were done this way: I think a great deal is that it just simplifies both the language definition and the compiler quite a bit. Rather than having to generate code separately for nearly every type, it does its best to collapse everything down to a few types, and most code is generated only for those types. That's not so much the case any more (now that we have multiple types larger than int), but back when C was young, the integer types were: char, short, int (and unsigned versions of those), so all the other types were promoted to int, and all code to manipulate anything was done with ints.

Note that this applied to function calls and such too: in early versions of C there were no function prototypes, so any parameter of type char or short was promoted to int before being passed to a function too.

The same basic idea was followed with floating point types: under most circumstances (including passing them to functions) floats were promoted to double, and all the actual processing was done on doubles (after which you could convert back to float, if necessary.


  1. In case you really want the quote for that too (§[conv.rank]:

1.3 A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
[...]
1.6 The rank of char shall equal the rank of signed char and unsigned char.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111