Jonathan Boccara's blog

Passing strong types by reference – First attempt

Published December 12, 2016 - 3 Comments

In this post in the series about strong types, we focus on the need for passing strong types by reference. This is a fairly common use case for strong types, since passing arguments by reference is so common, but I haven’t seen this aspect of strong types treated anywhere else yet.

This post is part of the following series:

As explained in the second post of this series, strong types give a specific meaning to generic types such as doubles and ints. We saw how it made interfaces stronger and code more expressive, and the implementation of strong types we described was a parametrized thin wrapper called NamedType:

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

Problem statement

Now you’ll notice that when a NamedType object is constructed from its underlying type, the underlying object has to be copied. NamedType is used for passing parameters to functions, so with this implementation the function receiving a NamedType parameter always gets a copy of the underlying object that was passed by its caller.

This is ok in some cases, like when the underlying type is a native type such as double or int, because they would have been passed by value anyway.

But for the general case this is an issue, because the default mode for passing parameters to functions in C++ is by reference-to-const. Passing by reference-to-const can be preferable to passing by value for 2 reasons:

  • avoiding a copy of the argument. This can have an impact on performance when the type is expensive to copy (by doing memory allocation for instance) and if the copy occurs in a performance sensitive location of the code,
  • allowing binding on the argument. This is not really a case for functions, but some object methods may want to bind to a parameter (for instance an iterator object bound to a range parameter – we’ll explore the important topic of range in later posts, follow me at the bottom of the article to be notified)

So we need to let NamedType objects be passed by const and by reference.

Passing by const

In fact, the NamedType as it is above can already be passed by const and have a natural behaviour, that is resembling passing the underlying type by const. This is allowed by the const get method in its interface:

    ....
    T const& get() const {return value_; }
    ....

If a function accepts a const NamedType, it won’t be able to change its contents, because the only access it can get to the underlying type is by const reference.

So the real issue is passing by reference (or reference-to-const for that matter).

Passing by reference

The above implementation of NamedType structurally makes a copy of it. I’ve tried to add other constructors and parametrize the object in various ways, and I came to the conclusion that the simplest solution was to have a dedicated wrapper for references. If you’ve tried and got to a different solution, feel free to drop a comment to this post to discuss about this.

Anyway, this thin wrapper dedicated for references can be called NamedTypeRef and can be implemented this way:

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_;
};

Here the constructor builds an object with std::ref over the underlying value. std::ref<T> constructs an object that represents a reference to an object of type T, but with value semantics (which implies being copyable, which is not the case of T& that, when copied, rather copies the object it points to), and this makes it easier to manipulate.

The underlying object is stored in an std::reference_wrapper<T>, which is the type returned by the std::ref<T> function.

After construction, the NamedTypeRef is bound to the value it was passed, which gives it the semantics of a reference.

Of course, the declaration of a specific NamedTypeRef can be done with the same syntax as for a NamedType:

using NameRef = NamedTypeRef<std::string, struct NameRefParameter>;

Conceptually, NameRef is supposed to mean Name&. The “Ref” suffix means in a function interface that the parameter is meant to be passed by reference:

void printName(const NameRef name);

However, the Ref suffix has to be written at call site too:

std::string userInput = "jonathan";

printName(NameRef(userInput));

We would have preferred to write just Name instead of NameRef at call site, but this is the price to pay for the 2 benefits of passing by reference-to-const cited at the beginning of this article. When you don’t need them, you don’t have to pay this price and you can just use the basic NamedType wrapper that makes a copy.

Related articles

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

Comments are closed