Equality and Inequality
Equality Comparison for Optional
We will start with the equality comparison, namely the ==
operator. If we think about conditions under which we would
– intuitively – consider two instances of an optional to be equal, we can quickly come up with the following rules.
Two instances
a
andb
of an optional areequal
if and only
- if both have no values or
- if both have values, which are
equal
.
We can now derive test cases for these two cases, in which two optionals are equal.
Equality Comparison as a Member Function
C++ provides multiple mechanisms to implement the ==
operator. One of them is to define this operator as a class
member function. Using this mechanism we can now provide a first implementation to make these two tests pass.
This implementation will obviously make our tests pass but it obviously wrong, too: every two instances of an optional are equal according to this equality operator definition. We need more test cases.
These two test cases are forcing us to provide a proper implementation of the equality operator.
Now all tests pass again. But this implementation can do even more than just compare two optionals. We can also compare an optional with another value, which is not an optional – and it works. For example we can write this test, which will pass without any further ado.
This works, because anyValue
is implicitly converted to an optional using the appropriate constructor. An equivalent
implementation would be to convert anyValue
explicitly.
This is quite nice. But if we swap the variables, it will not compile.
This is because we defined the ==
operator as a member, so it acts like a member function. We actually can call the
==
operator like a member function. This notation is actually valid C++!.
Now it should be obvious why the second line does not compile: anyValue
is of type unsigned int
which is not a class
type and therefore we can not call functions on it.
One could argue, that this doesn’t matter too much. A user should just use the comparison
operator with operands in
the correct order. But this is undesirable. Users will usually expect that they can exchange the operands as the want.
Equality comparison should be commutative. In order to archive this, we can define the operator as a
non-member-function (as recommended by C.161 of the C++
Core Guidelines).
Equality Comparison as a Non-Member Function
First of all we will adapt our new test from above, which tests the comparison of an optional with a non-optional,
so that both cases – x == anyValue
and anyValue == x
are tested.
Now our tests are failing and we are forced to adapt our implementation.
Please not the friend
specifier in the declaration. It is not strictly necessary to declare ==
operator as a friend
function. In our particular case a simple free function would be sufficient, because all necessary information are
accessible using the class’ member functions. For our current example, the choice between a friend or a non-friend function
comes down to a matter of taste, but I see the following advantages of the friend function approach (which hold for every
comparison operator):
- It will work in every case, even if not all necessary information are accessible via the class’ public interface. For consistency reasons, it might make sense to always declare comparison operators as friends. This is easily enforcable by using static code analysis tools like SonarQube.
- Even if a comparison operator can be implemented using the class’ public interface, it might still be desirable to prefer the friend over the non-friend function, because it doesn’t put additional requirements on the class’ public interface: getter-like functions can be dropped without breaking the comparison operator.
- A friend function can be placed within a class definition just like every other function, which is part of the class’ public interface. This might help clarity as the class’ interface is defined completely in one place. Additionally, C.168 is fullfilled without further ado.
- In generic code, idioms and implementation techniques like “Making New Friends”, the “hidden friends idiom” “Barton–Nackman trick” are also relying on friend functions, so if might help with out transition to a generic optional.
A Compatible Inequality Comparison Operator
In C++ the inequality comparison operator !=
is not automatically generated if the equality comparison operator is
defined. Therefore we need to implement it by ourselfs. At first we will, of course, adapt out tests first. In this
case, it seems reasonable to add just another assertion using the !=
operator to our tests regarding the ==
operator. For example, for our first test regarding the equality comparison operator, it looks like this.
As our tests are not compiling anymore, we know can add the implementation of the !=
operator.
We will implement it as a friend function, too. The implementation is actually pretty simple because we can delegate
most of the work to the ==
operator.
Conclusion
With the implementation of the equality and inequality comparison operators for our optional, we’ve made a big step in order to make our optional regular. We’ve seen multiple ways of implementing them and concluded to use the friend-function approach. Well will continue to make our optional a regular type in our next post by adding the missing comparison operators.