But you can't add 2 void pointers and seamlessly get integer addition if they point at integers or concatenation if they point at strings.
(You could build your own custom data types that have type metadata in a shared header and an addition function that uses it, but then you're building your own custom language on top which isn't really the same thing.)
So yes C really does restrict you in some ways that Javascript doesn't.
Pedantically speaking, you can't. You can have a variable that points to an integer 5, and then make it point to a character array "hello\0". But the value of the variable is a pointer in both cases.
Curiously enough, this is also true of Python - just less obvious because it doesn't have any variables that aren't pointers, and most operators perform an implicit dereference.
> Pedantically speaking, you can't. You can have a variable that points to an integer 5, and then make it point to a character array "hello\0". But the value of the variable is a pointer in both cases.
Unions may be the better analog here.
> Curiously enough, this is also true of Python - just less obvious because it doesn't have any variables that aren't pointers, and most operators perform an implicit dereference.
That's more like an implementation detail than a quality of the language itself. A JavaScript implementation might do the same. For example, values in V8 are all either pointers to the heap except in the case of small (31-bit) integers, and a less optimized implementation might not even make that distinction and allocate everything on the heap. Similarly, a Python implementation might store SMIs directly where the pointer would be, like V8. PyPy uses both tagged pointers/SMIs and may even allocate registers for values.
> That's more like an implementation detail than a quality of the language itself.
It is not, though, because - unlike JavaScript - the fact that everything is a reference to object, and each object has a unique identity, is explicitly a part of Python semantics, and this is very visible in many cases. It's easy to observe it for "primitive" types as well simply by inheriting from them.
(OTOH the fact that a given Python implementation might still implement this by using tagged pointers etc is an implementation detail, because it is still required to behave as-if everything was an object).
> Tagged unions would be, except they aren't first class in C.
For the originally stated example, 'declare a variable, set it to 5 (number), and then set it to the "hello" (string)', a plain union is just fine.
> The best analogy here would probably be OCaml polymorphic variants
It would be, except OCaml is not C.
> It is not, though, because - unlike JavaScript - the fact that everything is a reference to object
Evidently, references and pointers are not the same thing, hence it is an implementation detail whether references correspond to pointers.
> and each object has a unique identity
Which doesn't necessarily have anything to do with their address. It's an opaque value guaranteed to be unique for an object for the duration of its lifetime.
Meanwhile, C pointer values aren't necessarily unique for different objects. For example, a pointer to a struct with one or more fields shares value with a pointer to its first field. It's only once you factor in type that pointers uniquely identify a particular object, so they don't exactly correspond to Python's object identity.
Should you? Different question entirely.