A templated optional
A concrete optional
Since at least the last post our optional is finally (at least somewhat) feature complete and ready for use.
- It serves a specific purpose (which is holding either a value or no value at all) defined by its invariant.
- It has a interfaces which is arguably easy to use correctly and hard to use incorrectly.
- It is regular, which allows for broad use, e.g. in combination with the STL.
- It is const correct.
But out optional is limited the values of type unsigned int
. This is unfortunate: technically – especially in C++ –
there is no reason to limited our self to just one type: we can use
C++’s templates.
A generalized optional
Currently, the value type used in our optional unsinged int
is spread all over our implementation; most importantly in
the declaration of its mValue
but additionally in the constructor and the value()
member function.
In order to make out optional work with any other type, we need to replace all occurrences of unsigned int
with a
placeholder. Let’s call it T
(for “type”). This can be done by simple text replacement; basically every test editor or
IDE can do this.
But this isn’t enough. The compiler will complain, that T
“does not name a type”. We need to tell the compiler, that
T
is just a placeholder for a type. This is done by using the template
keyword in front of the class definition.
This keyword turns a class into a template class.
In the angle brackets <
and >
the template’s list of parameters is given. If a parameter is mean to denote a type,
either typename
or class
must precede the name. I prefer typename
over class
, because it is a bit more precise,
as T
actually names a type and is not necessarily a class
.
Now our optional is a template, but with a rather silly name! It doesn’t make any sense to have _unsigned_int
as part
of our optionals name, so we can just remove it.
Please note, that we are using optional
instead of optional<T>
in the definitions of the signatures of the
comparison operators. We can do this, because we are in the definition of optional<T>
. If we had defined these
functions outside of the class definition, we couldn’t do that. Inside of this class definition optional
and
optional<T>
can basically be used interchangeably. I decided to use the former, because it is simpler and less
repetitive.
Now our optional is optional<T>
instead of optional_unsigned_int
. But now, our test won’t compile, because the are
written in terms of optional_unsigned_int
, which is not defined anymore. For the time being, we can fix this by
introducing a typedef
for our tests.
Now our tests are working again. Actually, from a testing point of few, we didn’t change anything. optional<unsigned
int>
will actually basically generate the same code as optional_unsigned_int
did. If we use optional<unsigned int>
, the
compiler will replace all occurrences of T
in the class definition with unsigned int
, hence instantiate the
template class. This instantiantion of the template class will behave like the original optional_unsigned_int
did.
Essentially, the compiler will roll back our changes, we did to our implementation in this post.
We can actually (at least for such simple cases without template specializations
) think of template instantiation as
simple replacement. In The Design and Evolution of C++ Bjarne Stroustrup explains,
that before he invented templates, he tried to solve similar problems by using the C preprocessor (which does basically
nothing else then test replacement). He invented templates to overcome some of the weaknesses of this approach. If you
want to dive deeper into this topic, I recommend reading this book to you. It gives great insights in the early history
of C++ and explains really well the reasons, why many things in the language are like they are.
Conclusion
Well, that was easy! Replacing unsigned int
with a T
, adding a template<typename T>
and introducing a better name
was enough to generalize our optional. Actually this post was intended to take a bit of the fear about writing template
classes from you: it is basically as easy as this; at least it can be.
But this generalized optional is far from being optimal. For build-in types like int
, char
or float
is will work
quite well, though. But for other – more complex – types, this is not the case. In the upcoming posts, we will have a
look at the issues, which may arise with this rather naive implementation as soon as we are using it with such more
complex types.