> It is never "just an int". Soon you will ask questions about the int, and in a sufficiently large application you will forget the answers to the quesions easily, and scatter the same question (i.e., the same boolean logic) all over the place. Consider an application where you need to filter something by year. A year, you know? As in 1974, 2018, etc. An int is bad, because an int could be -5, which does not make sense. Ok, a uint then? Still bad, because then there is a year 0 and - if your application deals with birthdays of real people, 1800 would be an invalid birthday. The semantics (or pragmatics, linguistically speaking) emerge from how you interpret the integer, and this interpretation can happily live in that one file with that one class that encapsulates "just" that integer.

Are you honestly suggesting making a Year class to represent years in a C++-like language? And you want to forbid Years from being -5 or 1800, because they "do not make sense"?

What if I want to interpret the integer in a way you didn't think of when you wrote your Year class? Maybe I want to know whether my friend John was older when his sister Sally was born than Jesus was when St. Paul was born?

> What if you start with a 32bit integer and suddenly you realize that your weird Internet-Of-Things-Sensor reading database has grown and the 32bit integer is getting too small? You could just use 64bits. With "just an int", you now get to change every single function that expects an int for the purpose of a SensorReadingId (or an account id or whatever) and change it to size_t, uint, int64 or whatever.

The work needed to push the buttons on your keyboard to make the changes is the least of your worries when you want to change the underlying data types used in a complex program. If you have a special SensorReadingId type and you change what it is, all your code breaks at once and you get a hell of a mess to untangle.

It may not be practical to enforce "ints that represent sensor reading ids are SensorReadingId type" across all of your code. You'll need to unpack your type to a primitive type whenever you talk to an external library or serialise SensorReadingId's to storage or the network. Even if you did enforce SensorReadingId everywhere:

- Arrays of SensorReadingId now double in size. Perhaps your code crashes at startup because some stack frame grew too large.

- If you serialise SensorReadingId's anywhere, changing the type broke binary compatibility with your old code. Or the network protocol you're supposed to be speaking. Or the library you're using.

- You probably had to make a toInt() or unpack() or similar function anyway so that people who want to get at information inside the value of the SensorReadingId can do so. You still have to look at all the call sites of these functions.

In reality, you're going to do this sort of change incrementally. You push the 64-bit integers through more and more code, taking care that the high bits of the integers make it through everywhere you've made the change.

> As for returning an AccountID: I'd like to point out that the Account is a strong and independent object, who can make his own decisions and does not need to return anything. Returning its id like that is actually breaking encapsulation. Still if you do it, it probably shouldn't be an integer.

Why is an Account making its own decisions? This sounds like the "circle that knows how to draw itself" antipattern. My friend and I want to talk about a certain Account; how do I arrange things so that the Account makes the decision to put an identifier for itself into a certain field in a certain protobuf message before I send the message to my friend?