That's a poor hack, it moves an invariant that could be enforced at compile time to not much more than a convention that has to be preserved by code review.
E.g. a colleague implements de-serialisation for your type but adds an empty constructor to make their life easier. You might not learn there's a hole in the boat before your first bug.
I wouldn't call it a poor hack. In a way it's a parser, which aren't really poor hacks, but can be abused. Sure, it's not perfect and I'd like it better if it was checked at compile time, but it's way better than using a simple int. Also, the moment you have a bug, tracking it is really easy: just list the functions that returns this specific type.
Yes, but this can be easily worked around by creating custom types that wrap the integer (and can be unwrapped on compile time) and some conversion functions. So slightly more tedious but saying one can't define complex constraints on static types is not quite correct.
This is not really true if you include ML languages. Most of the time you create a specific type for a type that is constrained. Ada has pretty good support for this and ML languages too. Once you have a specific type you make all your functions accept only VoterAge instead of Int.
I think this would work with every language that has nominal and not structural typing. If you have structural typing, you have to wrap the int. For example, this is "branding" in Typescript. I'm not sure if there is a performance penalty and how big it is though.