C++ Named Requirements: Compare

Introduction

Given two values $a$ and $b$, by comparing their values, there could be one of the three ordering relationships between them, equal, i.e., the order of $a$ and $b$ cannot be determined from their values, $a$ strictly appears before $b$, and $b$ strictly appears before $a$. It appears that we would have to define two functions, $\text{equal}(x, y)$ which tells us whether $x$ is equal to $y$ and $\text{comp}(x, y)$ which tells us whether $x$ strictly appears before $y$, in order to explicitly determine which of the three ordering relationship the two values $a$ and $b$ belong to.

Close examination reveals that we can define $\text{equal}(x, y)$ using $\text{comp}(x, y)$ and boolean relationships $\text{and}$ and $\text{not}$, since the three ordering relationships are mutually exclusive. Concretely,

$$\text{equal}(x, y) \triangleq \left(\text{not} \ \text{comp}\left(x, y\right)\right) \ \text{and} \ \left(\text{not} \ \text{comp}\left(y, x\right)\right)$$

In fact, in C++ STL, most containers take one single compare function that can be used for determining the ordering relationship. In this blog post, I would like to quickly discuss about the caveats of compare functions in C++.

Caveats

The compare in C++ really means whether the first argument of the call appears before the second in the strict weak ordering relation induced by this type.

Many C++ STL containers are ordered containers, such as std::set, std::map, std::multiset, std::multimap. They take one compare function in the constructors. For example, the default compare function is std::less, which means by default, when we iterate from the beginning to the end of the sequence, the values in the sequence are in ascending order.

Now, what if we want to reverse the order of the values in the sequence? In our case, we want to have a descending-ordered sequence for those STL containers. Should we use std::greater_equal for the compare function in the constructor? The answer is no. std::greater_equal is not a compare function, because it does not follow the strict weak ordering relationship property. The consequence of using non-compare functions for ordered STL containers is that determining whether the two values are equal will not work as expected. For example, testing equivalence in the STL container using std::greater_equal will be

\begin{align} \text{equal}(8, 8) &= \left(\text{not} \ \text{geq}\left(8, 8\right)\right) \ \text{and} \ \left(\text{not} \ \text{geq}\left(8, 8\right)\right) \\ &= \left(\text{not} \ \text{true} \right) \ \text{and} \ \left(\text{not} \ \text{true} \right) \\ &= \text{false} \ \text{and} \ \text{false} \\ &= \text{false} \\ \end{align}

which is incorrect.

The correct compare function to use to reverse the order of the values in the sequence will be using std::greater instead of std::greater_equal.

Conclusions

Always have compare functions return false for equal values.

References

C++ Named Requirements: Compare

https://leimao.github.io/blog/CPP-Compare/

Lei Mao

01-22-2022

01-22-2022