Strongly typed constructors
This is the first post of a series on the topic of expressive types, that is, types that express what they represent, rather than how they are implemented. By carrying meaning, these types improve the readability and safety of the code.
Here is the series about strong types:
- Strongly typed constructors
- Strong types for strong interfaces
- Passing strong types by reference
- Strong lambdas: strong typing over generic types
- Good news: strong types are (mostly) free in C++
- Inheriting functionalities from the underlying type
- Making strong types hashable
- Converting strong units to one another
- Metaclasses, the Ultimate Answer to Strong Typing in C++?
- Making strong types implicitly convertible
Motivation
You may have come across the situation where an object needs to be constructed in two different ways, but with the same type. As an illustration let’s take the example of a class modelling a Circle.
Say that this class has the responsibility of providing its circumference and area, and can be constructed with its radius:
class Circle { public: explicit Circle(double radius) : radius_(radius) {} void setRadius(double radius) { radius_ = radius; }; double getCircumference() const { return 2 * Pi * radius_; } double getArea() const { return Pi * radius_ * radius_; } private: double radius_; };
Now we want to add the possibility of providing the diameter to the circle instead of the radius.
The diameter is represented by a double, like the radius, and here comes the issue with the constructors: there would be 2 constructors with the same prototype, that is, taking a double:
class Circle { public: explicit Circle(double radius) : radius_(radius) {} explicit Circle(double diameter) : radius_(diameter / 2) {} // This doesn't compile !! ...
This is not valid code, because calls to the constructor become ambiguous:
Circle c(7) // is the radius 7 or is it the diameter ??
Note that we don’t have the same issue with setters:
void setRadius(double radius) { radius_ = radius; } void setDiameter(double diameter) { radius_ = diameter / 2; }
The above setters are not ambiguous, because setters carry a name (setRadius and setDiameter). The point of this post is to show you how to make constructors carry a name too.
Tag dispatching: not the best option
Some pieces of code solve this issue with tag dispatching. If you’ve never heard of tag dispatching you can just skip through to the next section. Otherwise you may want to read on, to understand why this is not the best option here.
The idea of tag dispatching is to add a parameter to each prototype, in order to disambiguate the calls. Each prototype would get a parameter of a different type, making them distinguishable at call site. The additional type doesn’t carry a value. It is just there to specialize the prototypes. Therefore, new artificial types are created, with neither behaviour nor data, such as:
struct AsRadius {}; struct AsDiameter {};
The constructors would then become:
class Circle { public: explicit Circle(double radius, AsRadius) : radius_(radius) {} explicit Circle(double diameter, AsDiameter) : radius_(diameter / 2) {} ...
And at call site:
Circle circle1(7, AsRadius()); Circle circle2(14, AsDiameter());
I see 2 drawbacks with this technique:
- it makes the syntax arguably more awkward,
- it doesn’t scale. If you have several constructors with several arguments you need to disambiguate, prototypes become bigger and bigger.
Carry meaning in the type
A better alternative is to use a more expressive type. When you think about it, what you really want to pass to the constructor is a radius (or a diameter). But with the above implementation, what you are actually passing is a double. True, a double is how a radius is implemented, but it doesn’t really say what it is meant to be.
So the solution is to make the type expressive, that is to say to make it tell what it represents. This can be done by building a thin wrapper around the type, just for the purpose of putting a label over it:
class Radius { public: explicit Radius(double value) : value_(value) {} double get() const { return value_; } private: double value_; };
And similarly, for diameter:
class Diameter { public: explicit Diameter(double value) : value_(value) {} double get() const { return value_; } private: double value_; };
Then the constructors can use them this way:
class Circle { public: explicit Circle(Radius radius) : radius_(radius.get()) {} explicit Circle(Diameter diameter) : radius_(diameter.get() / 2) {} ...
And at call site:
Circle circle1(Radius(7)); Circle circle2(Diameter(14));
Now, the two wrappers we wrote are very similar and scream for generalization, and this is the topic of the next post: strong types.
Related articles:
- Strong types for strong interfaces
- Passing strong types by reference
- Strong lambdas: strong typing over generic types
- Good news: strong types are (mostly) free in C++
- Inheriting functionalities from the underlying type
- Making strong types hashable
- Converting strong units to one another
- Metaclasses, the Ultimate Answer to Strong Typing in C++?
- Making strong types implicitly convertible
Share this post!