A concrete optional

Since at least the last post our optional is finally (at least somewhat) feature complete and ready for use.

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.

class optional_unsigned_int {
 public:
  // ...
  optional_unsigned_int(unsigned int value) {
  //...
  unsigned int value() const {
  //..
 private:
   //...
   unsigned int mValue;
};

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.

template<typename T>
class optional_unsigned_int {
 public:
  // ...
  optional_unsigned_int(T value) {
  //...
  T value() const {
  //..
 private:
   //...
   T mValue;
};

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.

template<typename T>
class optional_unsigned_int {
 public:
  // ...
  optional(T value) {
  //...
  friend bool operator ==(optional a, optional b) {
  //...
  T value() const {
  //..
 private:
   //...
   T mValue;
};

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.

typedef optional<unsigned int> optional_unsigned_int;

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.