3

Suppose I'm writing C or C++ code which deals with... ok, let's make it citizens in a state. In this state, citizens have numeric id's (not strings - numbers); and for reasons of performance, or compatibility with other software, it is assumed there can be less than 2^32 citizens.

Now, in my code, I have a bunch of functions which take or return a citizen's numeric index; and other functions which take or return a number of citizens (e.g. number of people who were naturalized as citizens last year).

My dilemma regards the types. Do I:

  • define a size-type, and use it for both student indices and numbers-of-students?
  • define an index-type, and use it for both student indices and numbers-of-students?
  • Define both, despite them actually being just aliases of each other?

I'm also not sure what name to use for them. It wouldn't be citizen, as that would be a data type describing a student. Should it be something like citizen_index or citizen_index_t? But then, what about the size type? It's not citizen_size, after all... so - num_of_citizens? num_citizens_t?

I want to do this in a consistent, convincing and non-contrived way, but it seems like I only have bad options. Am I missing something?

Note: I can't just use std::size_t due to the constraint I mentioned earlier, in case you were thinking of suggesting that.

3
  • Also you may make them some sort of strong typedef to avoid bugs in accidental use one instead of the other.
    – RiaD
    Mar 8, 2020 at 0:24
  • You assert that you can't use size_t although that might be the idiomatic solution. Which specific constraints make this inappropriate? Are you going for embedded platforms where size_t is too small?
    – amon
    Mar 8, 2020 at 10:49
  • @amon: For the sake of discussion - I have a fixed binary interface to conform to and I need to pass arrays of 32-bit numbers as student indices. Maybe it's to another, existing, library.
    – einpoklum
    Mar 8, 2020 at 11:24

1 Answer 1

6

Define both. Give them different names.

You're experiencing something akin to primitive obsession. It seems so tempting to spread around the idea that Id and count are the same type just because those types are the same size. But that's flat wrong.

These are domain ideas. They deserve their own names, and their own types if for no better reason than it'll help you remember that x holds a count of three citizens and not an index into your 3rd citizen. Preventing misusing x that way is exactly why we have strong types.

Keep in mind that the reason these types don't already exist in your language is because the language designers had no idea what your domain was going to be. So while they gave you things like ints and strings they also gave you the ability to define your own types, regardless of the size they happen to be.

It might seem like this is over engineering but within your domain this offers a lot of protection and clarity. However, that is also the limit. Often we have to communicate outside of the system we control and have to express our domain types in a form understood by systems that don't know our domain types. That's when it's acceptable to break down and admit that the Id and count are really 32 bits. But the bulk of your code doesn't need to know that. It shouldn't know. It shouldn't care. Which will be nice if your population grows.

1
  • I think claiming primitive obsession for a count is a bit excessive, but if OP is using C++ their own type could at least be a zero-cost abstraction. The minimal implementation would be class CitizenCount { size_t value; public: explicit CitizenCount(size_t n): value{n} {} operator size_t() const { return value; } }, i.e. an explicit conversion from the wrapped type for safety but an implicit conversion to the wrapped type for convenience.
    – amon
    Mar 8, 2020 at 10:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.