With the help of the awesome people on SO, I think I can answer my own question now:
Just to correct the notion that 0x80000000 can't fit in an int:
It is possible to store, without loss or undefined behavior, the value 0x80000000 to an int (assuming sizeof(int) == 4). The following code can demonstrate this behavior:
#include <limits.h>
#include <stdio.h>
int main() {
int i = INT_MIN;
printf("%X\n", i);
return 0;
}
Assigning the literal 0x80000000 to a variable is little more nuanced, though.
What the other others failed to mention (except @Daniel Langr) is the fact that C++ doesn't have a concept of negative literals.
There are no negative integer literals. Expressions such as -1 apply the unary minus operator to the value represented by the literal, which may involve implicit type conversions.
With this in mind, the literal 0x80000000 is always treated as a positive number. Negations come after the size and sign have been determined. This is important: negations don't affect the unsigned/signedness of the literal, only the base and the value do. 0x80000000 is too big to fit in a signed integer, so C++ tries to use the next applicable type: unsigned int, which then succeeds. The order of types C++ tries depends on the base of the literal plus any suffixes it may or may not have.
The table is listed here: https://en.cppreference.com/w/cpp/language/integer_literal
So with this rule in mind let's work out some examples:
-2147483648: Treated as a long int because it can't fit in an int.
2147483648: Treated as a long int because C++ doesn't consider unsigned int as a candidate for decimal literals.
0x80000000: Treated as an unsigned int because C++ considers unsigned int as a candidate for non-decimal literals.
(-2147483647 - 1): Treated as an int. This is typically how INT_MIN is defined to preserve the type of the literal as an int. This is the type safe way of saying -2147483648 as an int.
-0x80000000: Treated as an unsigned int even though there's a negation. Negating any unsigned is undefined behavior, though.
-0x80000000l: Treated as a long int and the sign is properly negated.