Ordering Optionals
Less Than Comparison for Optional
In order to make our optional regular, we need to provide an
implementation of the Less-Than operator <
. For two optionals with a value, it makes sense to employ their <
operator.
Also we already defined that two empty optionals are
considered to be equal. Therefore we only need to decide, how an optional with a value shall compare to an optional
without a value. std::optional
considers an optional without a value to be
less than an optional without a value.
So our rules for the <
operator are:
Given two instances
a
andb
of an optional,a
is less thanb
if and only
- if
a
has no value andb
has a value.- if both have values and the value of
a
is less than the value ofb
.
We can now derive test cases for these two cases.
TEST_CASE("An optional without a value is less then an optional with a value.") {
optional_unsigned_int x;
unsigned int anyValue = 10;
optional_unsigned_int y(anyValue);
REQUIRE(x < y);
}
TEST_CASE("An optional 'x' is less than an optional 'y' if the value of 'x' is smaller than the value of 'y'.") {
unsigned int anyValueX = 5;
unsigned int anyValueY = 6;
REQUIRE(anyValueX < anyValueY);
optional_unsigned_int x(anyValueX);
optional_unsigned_int y(anyValueY);
REQUIRE(x < y);
}
We need to provide an implementation for the <
operator now. Just as it was the case for the ==
operator, we could
implement it as a member function, but we will implement it as a friend function for similar reasons like we did it for
the ==
operator.
class optional_unsigned_int {
public:
// ...
friend bool operator <(optional_unsigned_int a, optional_unsigned_int b) {
return true;
}
};
This implementation will – similarly as out first implementation of the
==
operator
– obviously make our tests pass but it is obviously wrong. Again, we need to implement a few tests for the negative
cases. We could obviously write them down one by one, but we can also reuse our existing test cases: if two optionals are
equal, one can never be less than the other. So we will just extend the existing test cases with checks for the <
operator.
TEST_CASE("Two optionals without a value are equal.") {
optional_unsigned_int x;
optional_unsigned_int y;
REQUIRE(x == y);
REQUIRE(y == x);
REQUIRE(!(x != y));
REQUIRE(!(y != x));
REQUIRE(!(x < y));
REQUIRE(!(y < x));
}
TEST_CASE("Two optionals with equal values are equal.") {
unsigned int anyValue = 5;
optional_unsigned_int x(anyValue);
optional_unsigned_int y(anyValue);
REQUIRE(x == y);
REQUIRE(y == x);
REQUIRE(!(x != y));
REQUIRE(!(y != x));
REQUIRE(!(x < y));
REQUIRE(!(y < x));
}
Now our tests are failing, so we need to provide a proper implementation.
class optional_unsigned_int {
public:
// ...
friend bool operator <(optional_unsigned_int a, optional_unsigned_int b) {
if (a.mHasValue && b.mHasValue) {
return a.mValue < b.mValue;
}
return !a.mHasValue && b.mHasValue;
}
};
All our tests are now passing again.
If we now have a second look at our test cases, we may see that there is some redundancy:
the negative test cases for the ==
operator and the positive test cases for the <
operator can be merged.
It seems reasonable to add the checks for the ==
operator to the tests cases for the <
operator.
TEST_CASE("An optional without a value is less then an optional with a value (they are unequal).") {
optional_unsigned_int x;
unsigned int anyValue = 10;
optional_unsigned_int y(anyValue);
REQUIRE(x < y);
REQUIRE(!(x == y));
REQUIRE(!(y == x));
REQUIRE(x != y);
REQUIRE(y != x);
}
TEST_CASE("An optional 'x' is less than an optional 'y' if the value of 'x' is smaller than the value of 'y' (they are unequal).") {
unsigned int anyValueX = 5;
unsigned int anyValueY = 6;
REQUIRE(anyValueX < anyValueY);
optional_unsigned_int x(anyValueX);
optional_unsigned_int y(anyValueY);
REQUIRE(x < y);
REQUIRE(!(x == y));
REQUIRE(!(y == x));
REQUIRE(x != y);
REQUIRE(y != x);
}
The Other Comparison Operators
==
, !=
and <
are not the only comparison operators in C++, there are also <=
, >
and >=
.
Although they are not required for a regular type, it makes still sense to implement them, as users will usually assume,
that all of them are available if one of them is available.
Thankfully, we can implement all of them in terms of of the <
operator and also reuse our existing test cases.
TEST_CASE("Two optionals without a value are equal.") {
optional_unsigned_int x;
optional_unsigned_int y;
REQUIRE(x == y);
REQUIRE(y == x);
REQUIRE(x >= y);
REQUIRE(y >= x);
REQUIRE(x <= y);
REQUIRE(y <= x);
REQUIRE(!(x != y));
REQUIRE(!(y != x));
REQUIRE(!(x < y));
REQUIRE(!(y < x));
REQUIRE(!(x > y));
REQUIRE(!(y > x));
}
TEST_CASE("Two optionals with equal values are equal.") {
unsigned int anyValue = 5;
optional_unsigned_int x(anyValue);
optional_unsigned_int y(anyValue);
REQUIRE(x == y);
REQUIRE(y == x);
REQUIRE(x <= y);
REQUIRE(y <= x);
REQUIRE(x >= y);
REQUIRE(y >= x);
REQUIRE(!(x != y));
REQUIRE(!(y != x));
REQUIRE(!(x < y));
REQUIRE(!(y < x));
REQUIRE(!(x > y));
REQUIRE(!(y > x));
}
TEST_CASE("An optional with a value and an unsigned value with the same value are equal.") {
unsigned int anyValue = 1;
optional_unsigned_int x(anyValue);
REQUIRE(x == anyValue);
REQUIRE(anyValue == x);
REQUIRE(x >= anyValue);
REQUIRE(anyValue >= x);
REQUIRE(x <= anyValue);
REQUIRE(anyValue <= x);
REQUIRE(!(x != anyValue));
REQUIRE(!(anyValue != x));
REQUIRE(!(x < anyValue));
REQUIRE(!(anyValue < x));
REQUIRE(!(x > anyValue));
REQUIRE(!(anyValue > x));
}
TEST_CASE("An optional without a value is less then an optional with a value (they are unequal).") {
optional_unsigned_int x;
unsigned int anyValue = 10;
optional_unsigned_int y(anyValue);
REQUIRE(x < y);
REQUIRE(y > x);
REQUIRE(x <= y);
REQUIRE(y >= x);
REQUIRE(!(x == y));
REQUIRE(!(y == x));
REQUIRE(x != y);
REQUIRE(y != x);
}
TEST_CASE("An optional 'x' is less than an optional 'y' if the value of 'x' is smaller than the value of 'y' (they are unequal).") {
unsigned int anyValueX = 5;
unsigned int anyValueY = 6;
REQUIRE(anyValueX < anyValueY);
optional_unsigned_int x(anyValueX);
optional_unsigned_int y(anyValueY);
REQUIRE(x < y);
REQUIRE(y > x);
REQUIRE(x <= y);
REQUIRE(y >= x);
REQUIRE(!(x == y));
REQUIRE(!(y == x));
REQUIRE(x != y);
REQUIRE(y != x);
}
Based on these tests, we can now implement the missing operators <=
, >
and >=
by delegating – directly or
indirectly – to <
.
class optional_unsigned_int {
public:
// ...
friend bool operator >(optional_unsigned_int a, optional_unsigned_int b) {
return b < a;
}
friend bool operator >=(optional_unsigned_int a, optional_unsigned_int b) {
return !(a < b);
}
friend bool operator <=(optional_unsigned_int a, optional_unsigned_int b) {
return !(a > b);
}
};
Conclusion
All comparison operators are now implemented for our optional. Especially the <
operator finally permits use cases like
sorting a vector of optionals
void sort_them(std::vector<optional_unsigned_int>& opts) {
std::sort(opts.begin(), opts.end());
}
or using an optional as the key of a std::map
.
std::map<optional_unsigned_int, std::string> m;
m[optional_unsigned_int()] = "no value";
In an upcoming post, we will have a look at the remaining operations in order to make sure that our optional is regular.