Jonathan Boccara's blog

Strong types for strong interfaces

Published a few months ago - 12 Comments

Strong types are a popular topic in the C++ community. In this post I want to focus specifically on how they can be used to make interfaces clearer and more robust.

This post in the second one in a series of 4 posts:

  • Named constructors: how to give names to several constructors with the same prototype
  • Strong types for strong interfaces:a generalisation of the above, to make interfaces more robust
  • Passing strong types by reference: a particular usage of strong types
  • Strong lambdas: strong typing over generic types

 

Motivation

First of all, what is a strong type ? A strong type is a type that is specific to a kind of usage, and that carries meaning through that specialization. As opposed to strong types would be general-use types, like native types such as ints and double for example. Often, native types don’t tell much about the meaning of their instances.

To illustrate this, let’s take the example of a class modelling a Rectangle. Say that a Rectangle can be initialized with a width and a height. To write this as an interface, the first idea that comes to mind is to use doubles:

doubles are a fairly generic type, so as per our above definition they don’t constitute a strong type here. But from anything we can see in this piece of code, we have to say that there seems to be nothing wrong with it.

The problem with too generic types appears at call site, when calling the above interface:

For a reader of this call to the constructor, there is absolutely no indication which one of 10 or 12 is the width or the height. This forces the reader to go check the interface of the Rectangle class, that is presumably located away in another file. For this reason, the usage of too generic types is detrimental for readability, and for no good reason: the code knows very well that 10 is the width and 12 is the height; it just won’t say it to you.

Additonnally, there is another issue with this Rectangle interface using doubles: nothing prevents the caller from passing the parameters in the wrong order. For example, the following will compile:

Making strong types

To solve this obfuscation of the code, one solution is to show the meaning of the parameters, at call site.

This is what strong types do. In the first article of this series, we encountered the need to write out a name over some parts of an interface, in the particular case of constructors. And to do this, we built a thin wrapper around the native type, for the sole purpose of giving it a specific name. To show that a particular double was meant to represent a Radius, we wrote the following wrapper:

Now it clearly appears that there is nothing specific to doubles or radii in this idea. It is therefore natural to write a generic component that would do the wrapping of a given type T. Let’s call this component NamedType:

(this is not the final implementation – see bottom of this post)

The occurrences of doubles have been basically replaced by the generic type T. Except for passing and returning the value, because even though doubles are passed by value, in the general case for a type T passing parameters to a method is done by reference-to-const.

There are several approaches to instantiate a particular named type, but I find the following one quite unambiguous:

Some implementations use inheritance, but I find the above is more expressive because it shows that we conceptually just want a type with a label put on it.

Using phantoms to be stronger

If you think about it, the above implementation is in fact not generic at all. Indeed, if you wanted to have a specific type for representing Height, how would you go about it ? If you did the following:

we would be back to square one: Width and Height would only be 2 aliases for NamedType<double>, thus making them interchangeable. Which defeats the point of all this.

To solve this issue, we can add a parameter, that would be specific for each named type. So one parameter for Width, another one for Height, etc.

Said differently, we want to parametrize the type NamedType. And in C++, parameterizing types is done by passing template parameters:

Actually the Parameter type is not used in the implementation the class NamedType. This is why it is called a Phantom type.

Here we want a template parameter for each instantiation of NamedType that would be unique across the whole programme. This can be achieved by defining a dedicated type each time. Since this dedicated type is created for the sole purpose of being passed as a template parameter, it does not need any behaviour or data. Let’s call it WidthParameter for the instantiation of Width:

In fact, WidthParameter can be declared within the using statement, making it possible to instantiate strong types in just one line of code:

And for Height:

Now Width and Height have explicit names, and are really 2 different types.

The Rectangle interface can the be rewritten:

Note that the parameter names are no longer needed, because the types already provide all the information.

And at call site, you have to state what you’re doing:

Otherwise the code won’t compile.

Strong types and user-defined literals

This plays well with user defined literals and units. To illustrate this, let’s add a unit for expressing lengths in meters. A meter is just a numeric value with a specific meaning, which is exactly what NamedType represents:

NamedTypes can be combined, and width and height can take a unit this way:

If we add a user-defined litteral for meter:

(to cover floating point literals, another overload should be also added for long double)

then we get a code at call site that is quite pretty:

Conclusion & to go further

Strong types reinforce interfaces by making them more expressive, especially at call site, and less error-prone by forcing the right order of arguments. They can be implemented by the following thin wrapper:

that can be used the following way:

To go deeper in this useful and popular topic, you can explore the following aspects:

On my side I will cover the passage of strong types by reference. Indeed, all of the above implementations perform copies of the underlying types each time they are passed to an interface, but in some cases this is not what you want. I haven’t seen this aspect of strong types treated anywhere yet, so it will be the focus of the last post in our series on strong types.

Related articles:

Liked it ? Share this post ! Facebooktwittergoogle_plus    Don't want to miss out ? Follow:   twitterrss

Subscribe to the newsletter