/* 'a' : int
* 'a' is truncated to char (self-evidently safe here(
*/
char c = 'a';
/* 1 : int
* c : char
* c is promoted to int
* 1 + c : int
* 1 + c is truncated to char
*/
char d = c + 1;
/* d : char
* c : char
* d and c are promoted to int
* d - c : int
* d - c is truncated to char
*/
char e = d - c;
If you want to avoid this behavior, you have to use the type suffixes explicitly, pretty much like that "(((2int + 2int)int) / 8int)int" expression you're making fun of. // Zero. 32-bit integer left shifted by 32 bits is always 0.
1 << 32;
// 2^32. "U" makes it unsigned.
// "LL" makes it "long long", 64-bits on Windows and Linux.
1ULL << 32;
// -2147483648 (signed 32-bit)
// i.e. 0x10000000
1 << 31;
// 2147483648 (unsigned 32-bit)
// i.e. 0x10000000
1U << 31;
I don't think this is a good thing. It's very confusing.I prefer the type inference approach, e.g. in Rust, where they're i32 if the type cannot be inferred and the literal has no type suffix. And I like that no two integers can be used by the same binary operator unless they have the same type, so you need to explicitly cast them to the right type.
That is not type inference. 1 has the type int. 2.0 has the type double. Then, 1 gets converted to double. Then the whole expression has the type double. This isn't type inference; this is like saying JavaScript has type inference because it deduces that the expression ('4' - true) has the "number" type (i.e. double precision floating point).
Compare with Haskell, where a numeric literal like 32 has the type (Num a) => a, i.e., it's polymorphic, and the type is actually inferred based on the context it's used in (it could be Int, Integer, Double, Rational, whatever). If you ask it the type at a REPL, it just tells you "32 :: Num a => a", whereas C would tell you that 32 has the type int (if there were a REPL for C).
This is just a difference in terminology. To me, what you’re describing is exactly how a type inference algorithm works. This is also the traditional academic definition of type inference, but it sounds like you’re just using it to mean “inference of the types of variables” (which makes sense as the programmer-facing definition, to be fair, because basically all languages have type inference in other places)
> This is like saying JavaScript has type inference
JS doesn’t have type inference because it’s not statically typed, but Typescript does, and it works exactly how you described it.
The difference is, with compiled languages, the compiler needs to know the type of every expression and subexpression ahead of time, to know what code to emit.
Like, in Rust, the type of a literal depends on the context of the variable it's later used as.
// Due to usage below first line,
// 5 is retroactively reanalyzed as if it were 5u8
let a = 5;
let b: u8 = a + 1;
Whereas in C, literals always have a set type, and the only reason you can use, e.g., int literals in non-integer expressions is due to the great amount of implicit type conversions in C.C++, which has a form of type inference, works differently: the type of a variable is always the same as the type of expression initializing it. The closest equivalent in C++:
// "a" is inferred to be an "int" because 5 is always of type "int"
auto a = 5;
uint8_t b = a + 1; // Implicit truncation from "int" to "uint8_t"
There true equivalent of an integer literal 5 from C (on 64-bit Linux) in Rust would be 5i32, because it's always the same sign, type, and size in every expression. There's never any doubt about the type of an expression or a literal, and hence no need for some type inference algorithm, only implicit conversions. Hence, the equivalent in Rust of the above C++ is this (depending on the platform): let a = 5i32;
let b: u8 = (a + 1i32) as u8; int(int(int(2) +::<int> int(2)) /::<int> int(8))
We must disambiguate integer- and float-point arithmetic operators for sure.That's not why you don't have to do that.
Many (most statically typed?) Algol-likes have strict types for literals (e.g., 2 is always a signed int, if you if you want specifically unsigned you might say 2u and if you want a double you say 2.0, and if you want a single-precision float you say 2.0f, or something) and strict rules at how math between them works and what types it produces, this has been true since long before tyoe inference became common, and is why you don't have to say 2int+2int — 2 is syntactically defined as int.
There is no inference