A null pointer for optional
Assign for reset
Recently we we introduced optional::reset()
to make any optional
empty. To some extent we did this to be consistent with the
smart pointers
as they also have such reset
function. It runs out that there is a mechanism for archiving the same
thing as call reset()
on a smart pointer and which works for raw pointers, too: we can assign a
null pointer (nullptr
). It would be quite nice to have something similar for out optional, too!
Something like a nullptr for optional
In order to make clear what we want to archive, let’s have a look at a simple example first!
For std::unique_ptr
in C++11 we could write something like this:
Please note that this only possible in C++11 not only because std::unique_ptr has been introduced in C++11 but also because nullptr has been added to the language in C++11, too.
In C++11 there are two new things with regard to null pointers:
- the type std::nullptr_t and
- the literal nullptr of type
std::nullptr_t
.
Both are important here. nullptr
(the literal) is needed in order to be able to write the code above.
The type std::nullptr_t
allows the definition of an overload of unique_ptr
’s assignment operator for
exactly that case. This overload of the assignment operator has the same effect as calling reset()
.
We need something similar for optional
. We can and should not use nullptr
though because:
- As we are still using C++98 we cannot use
nullptr
, because it’s not available yet. - In case of a function overloaded for a pointer and a optional this will cause an ambiguity – we will get back to that.
So we need to come up with our own solution for that: we need another type and a constant for this reason.
This is what nullopt_t and
nullopt. Defining nullopt_t
is quite easy: an empty
struct
will suffice. Given that, we can already implement the new overload for the assignment operator.
Please note, that the overload – just as the original one –
return’s a reference to the optional it self. This enables a notation like a = b = nullopt
.
Based on the test cases for reset()
we
already introduced before we can derive new test cases for this new
assignment operator.
Another constructor
We can now write this code:
But we cannot write this code:
The reason for that is, that in this case the assignment operator is not used: we need another constructor for this case.
We can easily derive a suitable test case…
… and add the missing constructor.
Equality
Assignment and construction from nullopt
is now possible – just as we can assign a null pointer to any other pointer.
But what else can we do with a null pointer? We can use it in order to check whether a pointer stores a null pointer!
In order to extend our analogy between pointers and optionals even further it makes sense to have the ability to check
whether an optional is empty by comparing in with nullopt
. As usual we will write tests covering this behavior first:
If we try to compile our tests now we will get a compiler error:
The reason for that are the overloads for the ==
and !=
operator we had introduced as an optimization for
heterogeneous comparisons: the non-optional parameter will
happily take anything we pass to – including nullopt
. Therefor the respective overload will be chosen which then
causes compilation to fail because nullopt
can usually not be compared with the value type.
Hence, we need to provide separate overloads for these cases:
With these two our tests compile successfully and all our tests succeed.
Please note that we didn’t need to provide the respective overloads for !=
. The reason for that is
that we implemented the heterogeneous overloads
of the !=
operator in terms of the ==
operator. If we try to compare an optional with a nullopt
using
the !=
operator, the compiler will take the heterogeneous overload of it – just as it did it for ==
above.
But in this case the compilation success because here we are delegating to the ==
operator, which we just
had implemented.
The Other Comparisons
As we now have ==
and !=
for nullopt
it makes sense to also introduce the over comparison operators.
We can treat nullopt
just like an optional without a value, so that the same rule for the comparison operators
apply: an nullopt
is always considered to be smaller than an optional with a value. Given that we can add
appropriated tests rather easily:
Just as we did before I’ve chosen to extend the equality and
inequality test case a bit in order to avoid duplication. Based on that we can now implement the <
operator.
The new tests will compile and succeed now. There are two things noteworthy here:
- again, it is enough to add overload to the
<
operator. This is (again) because of the heterogeneous comparisons and the fact that the other comparison operators are implemented in terms of<
. - We have an interesting asymmetry here: it depends on the order of the operands whether the optional matters or not. If we give it a seconds thought, this makes totally sense: an optional can never the less than an optional without a value. If can be greater or equal to it but never less.
Conclusion
Once again we took the syntax of pointer as a guiding line for extending the design of optional: we introduced nullopt
as an null pointer equivalent. We anticipated the introduction of nullptr
in C++11, but we could implement
nullopt_t
and nullopt
in C++98 already. We can now write:
optional<T> x; x = nullopt
,optional<T> x = nullopt
,x == nullopt
and e.g.x < nullopt
We introduced nullopt
as a global variable. For the moment this works quite well, but in general
globals may cause issues if
used in the context of shared and static libraries.
However, we will have a look at this in an upcoming post.