Jonathan Boccara's blog

Strong types: inheriting the underlying type’s functionalities

Published May 23, 2017 - 5 Comments

This post is part of the series on strong types:

Until recently, I thought there was little point providing extra functionalities to strong types. Like being able to add them or subtract them or print them, for example, by reusing the underlying type’s capabilities. I thought that because to me, strong types were very useful to make interfaces more robust and expressive, and their usage would stop there. The implementer of such an interface would pick up the underlying value with .get() and carry on with their code. And that adding such functionalities would only induce more complexity through more code. YAGNI, if you like.

And then I watched this talk about std::chrono from Howard Hinnant, where he shows how the standard library now manipulate time-related values, such as second, milliseconds and hours. And I realized that I was wrong and that in fact, YAGNI. But this time a YAGNI that says You Are Gonna Need It, not the other one.

The talk is not about strong types per se, but they are in fine print all the time. Indeed a second, for instance, is just an int over which a special meaning of “second” has been added, by using the type system. This is effectively strong typing. And one of the things that Howard Hinnant pointed out is that you want to be able to subtract two time points (that are essentially ints strongly typed to represent the time passed since a given origin) , to obtain a duration, representing how much has passed between such and such moments in time.

And obviously in this case we certainly don’t want to write this:

Duration duration = Duration(t2.get() - t1.get());

Instead we’d be better off with:

Duration duration = t2 - t1;

where t1 and t2 are time points.

Another example is adding seconds together. This is something you want to be able to do without having to manually wrap and unwrap intermediary results. And contrary to the duration example that needs a specific time-related logic, implementing the addition of seconds is basically just adding ints together.

Hence the need to inherit some operators from the underlying type.

Inheriting operators

The first thing to note is that we don’t want to inherit all of the underlying type’s functionalities. For example seconds should arguably not be multiplied together, even though ints can be. So we want to be able to choose which functionalities to inherit from.

To selectively inherit functionalities, we will use C++ inheritance.

Before jumping into the implementation I want to note that I got inspired by foonathan’s blog, and in particular by this great post about strong types. Jonathan also uses inheritance to recycle functionalities, although the implementation I will propose is different enough for me to think it’s worth writing about, particularly regarding the declaration of the strong type. Anyway you may know his blog already since it is quite popular in the C++ community, and if you don’t then you should definitely check it out, as it really has great contents.

Each underlying functionality, like adding, subtracting, printing and such can be isolated in a separate class, a policy, that the strong type may choose to inherit from. The generic class we want to inherit from needs the actual strong type to perform its functionalities. So the strong type inherits from a class that needs it. This is precisely a use case for CRTP. For this reason we will use the crtp helper described in this post on CRTP, that gives an easy access to the type that is inherting from the base class:

template <typename T, template<typename> class crtpType>
struct crtp
{
    T& underlying() { return static_cast<T&>(*this); }
    T const& underlying() const { return static_cast<T const&>(*this); }
};

Now the following class represents the possibility of adding two instances of a named type together, by performing a sum on their underlying type:

template <typename T>
struct Addable : crtp<T, Addable>
{
    T operator+(T const& other) { return T(this->underlying().get() + other.get()); }
};

and can be used this way with the generic NamedType class described here:

template <typename T, typename Parameter>
class NamedType : public Addable<NamedType<T, Parameter>>
{
public:
    explicit NamedType(T const& value) : value_(value) {}
    T& get() { return value_; }
    T const& get() const {return value_; }
private:
    T value_;
};

Then the following declaration of strong type of a length:

using Length = NamedType<double, LengthParameter>;

allows lengths to be added together, returning a new length:

Length total = l1 + l2;

We can add other capacities, like incrementing:

template <typename T>
struct Incrementable : crtp<T, Incrementable>
{
    T& operator+=(T const& other) { this->underlying().get() += other.get(); return this->underlying(); }
};

Multiplying:

template <typename T>
struct Multiplicable : crtp<T, Multiplicable>
{
    T operator*(T const& other) { return T(this->underlying().get() * other.get()); }
};

or printing the underlying value:

template <typename T>
struct Printable : crtp<T, Printable>
{
    void print(std::ostream& os) const { os << this->underlying().get(); }
};

template <typename T, typename Parameter>
std::ostream& operator<<(std::ostream& os, NamedType<T, Parameter> const& object)
{
    object.print(os);
    return os;
}

But all capabilities don’t make sense for all instantiations of strong types. For instance multiplying may not make much sense for a length. We would like to be able to choose, for each instantiation of a NamedType, which capabilities it should inherit from.

The pick-and-choose interface

The declaration of strong types we’ve used so far was:

using Length = NamedType<double, LengthParameter>;

A nice way of declaring the capabilities of a strong type would be:

using Length = NamedType<double, LengthParameter, Addable, Printable>;

with a list of functionalities that would be variable (potentially empty) and specific to each strong type.

How can this be achieved with the NamedType interface?

This is actually quite straightforward with a variadic pack of capabilities that the NamedType could inherit from:

template <typename T, typename Parameter, template<typename> class... Skills>
class NamedType : public Skills<NamedType<T, Parameter, Skills...>>...
{
public:
    explicit NamedType(T const& value) : value_(value) {}
    T& get() { return value_; }
    T const& get() const {return value_; }
private:
    T value_;
};

And this does it!

For instance, with the type Length declared above, the following compiles:

Length x(5);
Length y(7);

std::cout << x + y << "\n";

While the following does not:

Length x(5);
Length y(7);

std::cout << x * y << "\n";

which is what we aimed for.

Go strong types !!

Related articles:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed