Jonathan Boccara's blog

Making Strong Types Implicitly Convertible

Published January 5, 2018 - 0 Comments

Strong types and implicit conversions, doesn’t this sound like incompatible features ?

It can be argued that they are compatible, in fact. We saw why it could be useful to inherit from the underlying type’s features, and if the underlying type is implicitly convertible to something then you might want to inherit that feature too for your strong type.

In fact, NamedType user Jan Koniarik expressed on Twitter a need for exactly this feature for the NamedType library. I think the need is interesting, and some aspects of the implementation are worth considering too; which is why I’m sharing this with you today.

This article is part of the series on strong types:

Strong types implicit conversions

Adding an ImplicitlyConvertibleTo skill

The functionalities inherited from the underlying type, also named “Skills” in the NamedType library, are grouped into separate classes using the CRTP pattern. For example, to reuse the operator+ of the underlying type the Addable skill looks like this:

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

The crtp class that this skill inherits from is a helper that gives easy access the underlying of the CRTP, that is the class that inherits from it. If you’re curious about this you can check it all out in the post on the CRTP helper.

If the type T that NamedType is strengthening is convertible, say to int, then we can implement a skills that performs an implicit conversion of the strong type to an int:

template <typename T>
struct ImplicitlyConvertibleToInt : crtp<T, ImplicitlyConvertibleToInt>
{
    operator int() const
    {
        return this->underlying().get();
    }
};

Fine. But int is a very specific case, our type T could be implicitly convertible to anything. It seems natural to template this class on the destination type of the conversion.

But there is a problem, this class is already a template! How can we template a class that’s already a template?

I suggest you pause for just a moment and try to think about how you would do it.

(🎶 musical interlude 🎶)

Done?

One way to go about this is to wrap this template class into another template class. This comes from a fairly common metaprogramming technique, whose naming convention is to call the inner template class “templ”. Let’s do this:

template <typename Destination>
struct ImplicitlyConvertibleTo
{
    template <typename T>
    struct templ : crtp<T, templ>
    {
        operator Destination() const
        {
            return this->underlying().get();
        }
    };
    
};

Since the underlying type can have implicit conversions, I think it’s right to offer the possibility to the strong type to inherit that feature. It’s just a possibility, your strong type doesn’t have to have an ImplicitlyConvertibleTo skill even if its underlying type supports implicit conversions.

The two directions of implicit conversions

We can now use this skill in our instantiation of NamedType. Let’s test it with a type A that is convertible to B because it implements an implicit conversion operator:

struct B
{

};

struct A
{
    operator B () const { return B(); }
};

Then a strong type over A could keep this propriety of being convertible to B:

using StrongA = NamedType<A, struct StrongATag, ImplicitlyConvertibleTo<B>::templ>;

B b = strongA; // implicit conversion here

There is another way for A to be convertible to B: if B has a constructor taking an A and that is not explicit:

struct A
{

};

struct B
{
    B(A const& a){}
};

The same usage of our ImplicitlyConvertibleTo skill works:

using StrongA = NamedType<A, struct StrongATag, ImplicitlyConvertibleTo<B>::templ>;

B b = strongA; // another implicit conversion here

You may have noticed the ::templ in the client code. This is really annoying, and I must admit I didn’t find a way to make it disappear. I would have loved to rename the real skill something like ImplicitlyConvertibleTo_impl and declare an alias for the simpler name:

// Imaginary C++
template <typename Destination>
using ImplicitlyConvertibleTo = ImplicitlyConvertibleTo_Impl<Destination>::template templ;

But there is no such thing as an alias for template templates in C++. I’m not entirely sure why, but I understand that this feature was considered by the C++ committee, but didn’t make it into the standard (yet?).

So for the moment let’s stick with the trailing ::templ in client code. If you see how to hide this, please, shout!

Not made for calling functions

At first sight, it seems that this sort of implicit conversion could be used to invoke a function that expects an underlying type by passing it a NamedType instead. Indeed, we could declare the NamedType to be implicitly convertible to its underlying type. This way we wouldn’t have to write a call to .get() every time we pass a NamedType to a function that existed before it:

using Label = NamedType<std::string, struct LabelTag, ImplicitlyConvertibleTo<std::string>::templ>;

std::string toUpperCase(std::string const& s);

void display(Label const& label)
{
    std::cout << toUpperCase(label) << '\n';
}

Indeed, without this skill we need to pass the underlying type taken from the NamedType explicitly:

using Label = NamedType<std::string, struct LabelTag>;

std::string toUpperCase(std::string const& s);

void display(Label const& label)
{
    std::cout << toUpperCase(label.get()) << '\n';
}

Of course, this stays an opt-in, that is to say you can choose whether or not not activate this conversion feature.

However, while I this implementation can be appropriate for implicit conversions in general, it isn’t the best solution for the case of calling functions on strong types. Indeed, looking back at our implicit conversion skill, its operator was defined like this:

operator Destination() const
{
    return this->underlying().get();
}

In the above example, Destination is std::string.

Given that this method returns an object inside the class by value, it creates a copy of it. So if we use this to call function, it means that we’ll pass copies of the underlying value as arguments to the function. This has the drawbacks of potentially making a useless copy, and to prevent the function to bind to an argument (which can be useful – std::back_inserter does it for instance).

No, ImplicitlyConvertible works for implicit conversions, but for allowing to call functions we need something different. Something which is detailed in Calling Functions and Methods on Strong Types.

Related articles:

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