Jonathan Boccara's blog

Passing strong types by reference

Published March 6, 2017 - 16 Comments

On Fluent C++ we had already considered passing strong types by references, and realized that this wasn’t such a simple thing to do. To understand why, I suggest you read the problem statement in this previous post before starting this one, so that we are in line.

So far the series on strong types contains the following articles:

In our previous attempt, we came up with this class that would be used exclusively to create strong types representing strongly typed references of primitive types:

template<typename T, typename Parameter>
class NamedTypeRef
{
public:
	explicit NamedTypeRef(T& t) : t_(std::ref(t)){}
	T& get() {return t_.get();}
	T const& get() const {return t_.get();}
private:
	std::reference_wrapper<T> t_;
};

that could be instantiated the following way:

using FirstNameRef = NamedTypeRef<std::string, struct FirstNameRefParameter>;

This works fine. But has the unpleasant disadvantage of creating a new component, different from the central one we made for representing strong types in C++: NamedType.

After I presented this work to various people, I got feedback and suggestions that steered me into another direction. The result is that we can actually represent references, strongly types, by using the NamedType class itself. Let me show you  how.

Strengthening a reference

A very simple way to represent a reference strongly type is to take the NamedType wrapper, made for adding strong typing over any type, and use it on a type that is itself a reference:

using FirstNameRef = NamedType<std::string&, struct FirstNameRefParameter>;

Simple, right?

Except this doesn’t compile.

Incompatibility with NamedType

The compilation error comes from the constructors in NamedType. Here is the NamedType class:

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

When T is a reference, say it is U&, then reference collapsing does the following when instantiating the template:

  • In the first constructor, T const& becomes U& const& which collapses into U&,
  • In the second constructor, T&& becomes U& && which collapses into U&.

If you are unfamiliar with reference collapsing, the last part of this excellent talk from Scott Meyers tells you everything you need to know to understand the above two lines.

Anyway, the bottom line is that the two resulting constructors take a U const& and a U& respectively, which is ambiguous, and the code won’t compile.

Making it compatible

A simple idea to make it compatible with NamedType is to remove the constructor by r-value reference if T is a reference. It would not make much sense to move a reference anyway, so this constructor is not needed in this case.

This can be achieved by using template meta-programming, and SFINAE in particular. I’m going to show you one way to do it, and then explain how that works, if you have an interest in understanding that bit. But it is important to realize that this can be considered as an implementation detail, because a user of NamedType can just instantiate its type with the above syntax and not worry about this removed constructor.

So here it is:

template<typename T_ = T>
explicit NamedType(T&& value,
    typename std::enable_if<!std::is_reference<T_>{},
    std::nullptr_t>::type = nullptr)
: value_(std::move(value)) {}

The central piece in this construction is std::enable_if that aims at “enabling” some code (in this case the constructor) only when a certain condition is true, if this condition is verifiable at compile type. And checking whether T is a reference can be checked at compile time. If this condition doesn’t hold then enable_if fails in its template substition. And as SFINAE would have it, Substitution Failure Is Not An Error. So the code compiles and the constructor just goes away.

One particular thing is that there has to be a substition, meaning there must be a template parameter. And from the perspective of the constructor, T is not a template parameter, because to instantiate the constructor T is already known. This is why we artificially create a new template parameter, T_, which is actually the same as T.

If you don’t fully understand the previous two paragraphs, or can’t be bothered to dig, it’s all right. The thing to remember is that you can just use the following class and wrap it around references:

template <typename T, typename Parameter>
class NamedType
{
public:
    explicit NamedType(T const& value) : value_(value) {}
    template<typename T_ = T>
    explicit NamedType(T&& value,
        typename std::enable_if<!std::is_reference<T_>{},
        std::nullptr_t>::type = nullptr)
    : value_(std::move(value)) {}

    T& get() { return value_; }
    T const& get() const {return value_; }
private:
    T value_;
};

All the code is on a GitHub repository if you want to play around with it. And more posts are to come, to describe new functionalities that turn out to be very useful to add to strong type.

This series is definitely not over.

Related articles:

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

Comments are closed