I admit. C++ Strongly-Typed Duck is a clickbait title. First a confession. This “strong type ” implementation is not my idea. Please make a slight detour into the direction of “Strong Type by Struct“.
Here we will try and formalize this idea in the realm of real-life C and C++ and intricate but significant differences between the two.
(the next episode of the “strong typing” mini serial is here)
This idea is basically yet another of those: “Just make everything to be a class”. Primordial C++ primordial idea. Rooted in SmallTalk. But, I don’t care if it is. It works. If used but not overused. There is no complex C or C++ behind it. Almost nothing. Just simple guidance:
Make every “strong type” as one struct, with a single member named ‘v’. Name of the struct is the strong type name. Type of the v is defined by you.
Let the code speak. From the start, we need to take care of C vs C++ differences. And that is the bush we need to beat. Not around it.
1 2 3 4 5 |
#ifdef __cplusplus #define STRONG(N,T) struct N final { using value_type = T; T v; } #else // C #define STRONG(N,T) typedef struct { T v; } N #endif |
That is my minuscule macro, that works for me.
1 2 3 4 5 6 7 |
// C++ STRONG(Age,int) ; // struct Age final { using value_type = int; int v} ; /* C */ STRONG(Age,int) ; /* typedef struct { int v} Age ; |
Now let us “jump into the deep”, to avoid tiring you with the prose, instead of delighting you with a code. The use case is “Flying Duck Institute” IT support. There will be measurements so let us do some physical units first. We do not want to mix kilograms and millimetres. So let us make them strong types.
1 2 3 4 5 |
// measurement units STRONG(Kg, double); STRONG(Mm, double); // struct Kg final { double v} ; // struct Mm final { double v} ; |
And now we have some duck specimens to measure. We go further and use the same ridiculously simple macro to produce clean code.
1 2 3 4 5 6 7 8 |
// NOTE: this is C++ // attributes of a specimen STRONG(Height, Mm ) ; STRONG(Weight, Kg ) ; // NOTE: those are not flat structures // struct Weight final { Kg v } ; // struct Height final { Mm v } ; |
We shall dive into C vs C++ significance shortly. For now, let us serve the “Flying Duck Institute” IT support. Look, here is the first duck flying in! Let us take care of recording the results of its measurement.
1 2 3 4 5 6 7 8 9 10 11 |
// in our insitute we measure and classify Duck specimens. // as a POC (Proof Of Concept) we will measure one Duck first: Duffy // C++ struct Specimen final { mutable Height height; mutable Weight weight; } ; // Same as every other creature, Duffy can not change (morph) // into some other Duck, even if they are both Duck's // C++ coder has made a mistake here ! constexpr Specimen duffy { {4.2 }, {430.23} } ; |
But. Wait a minute, that definition is a mistake! Obviously, height and weight are mixed up. Frankly that “declare and initialize” style has made it possible too. So let’s do it nice and simple:
1 2 3 4 5 6 7 8 |
// C++ int main() { Specimen duffy ; // h and w are a structure types duffy.h = { 4.52 }; // mistake again! duffy.w = { 430.23 }; // and again! } |
Huh? Is this a joke? That guy better takes some rest. He made a mistake again. But. In C that mistake simply can not happen. That is because C has “compound literals” and C requires compound literals to assign structure instances to structure instances.
1 2 3 4 5 6 7 8 9 10 11 12 |
/* C */ typedef struct { Height height; Weight weight; } Specimen ; void main ( void ) { Specimen duffy ; // h and w are a structure types duffy.h = (Height){420.2}; // compound literals constructs duffy.w = (Weight){4.52}; // unnamed objects of struct type in-place } |
That C code is plenty of opportunities to remind oneself what is oneself doing and not make a mistake in that realization.
This does not mean you need to drop C++ and jump onto C. After all you might find yourself very adverse to that C code. Just please be aware of the C++ issue with anonymous field designators. Let us proceed. Duffy is immutable, but it has gained in weight
1 2 3 4 |
// C++ // developer has re-focused and this // number is right this time duffy.weight = { 4.552 }; |
There are no guarantees here, one can indeed mistake height for weight when giving them values, especially in C++.
Why would anybody do this?
The key point is: we have described a Duck as a strong type. That is much (much) better than:
1 2 3 4 5 |
// C++ struct Duck { double h; double w ; } /* C */ typedef struct { double h; double w ; } Duck ; |
That is a simple code wide open to all sorts of bugs. And very difficult to maintain down the long timeline. For the opposite presented here, one can say: “Strong Duck” typing indeed.
- C version: https://godbolt.org/z/zfPYfK
- C++ version: https://cppx.godbolt.org/z/v7e1dW
Lastly, if we imagine we are talking about flying rockets, not ducks, we might start to realize the benefits of coding idioms which are making mistakes hard to make.