C++20 comparison in Qt (even with C++17🤩)
November 05, 2024 by Rym Bouabid | Comments
In the Qt 6.7 release, we enabled support for C++20 comparison and also back-ported some of its features to C++17. This blog post will give you an overview of the comparison enhancements we are taking advantage of and offer guidance on implementing them in your custom classes.
In a nutshell
C++20 introduced several new features and improvements. Notably, it changed the way comparison operators work in several ways that allow you to achieve more with less code.
The essential addition is the three-way comparison operator <=>, also known as the spaceship operator. It performs a single comparison operation and returns an outcome indicating whether the left-hand side (LHS) is less than, equal to, or greater than the right-hand side (RHS).
The three possible result types of the spaceship operator are std::strong_ordering, std::weak_ordering or std::partial_ordering, depending on the strength category implemented by the compared types. These three ordering categories offer more nuanced ways to compare values than a simple boolean result, as they not only express (in)equality but also the relative relationship between values.
- Strong ordering guarantees that values considered equal will behave identically in all regular operations, meaning every aspect of the values is identical except for their memory addresses.
- Weak ordering considers some values to be equivalent, though they differ in specific ways. Let's say we have two strings "Qt" and "qt". If you decide to compare them case-insensitively, then they are considered weakly ordered. Why? Because even though lhs[0] and rhs[0] are not exactly the same (Q and q), the strings are considered equivalent. Bonus info for you: this type is suitable for sorting.
- Partial ordering is more relaxed. It is useful when some values cannot be meaningfully ordered at all, like when comparing floating-point NaNs. In such cases, the result might be partial_order::unordered.
Another noteworthy feature is the ability to synthesize missing relational operators from the ones already defined. This, for example, makes operator!=() available thanks to operator==() and all other relational operators from operator<=>().
Additionally, for the mixed-type comparison, the order of arguments in both spaceship and equality operators does not matter because the compiler will also synthesize the reversed operators.
Ultimately, this simplifies the process significantly, since you no longer need to manually implement all those <, <=, ==, !=, >=, and > operators for your type. Instead, you only need to implement operator==() and operator<=>().
C++20 comparison support in Qt
As you may know, Qt 6.7 supports C++17 but can also be used with C++20. In light of this, we have made some C++20 comparison-related functionalities available to Qt users working with both versions.
First, we now have our own Qt's comparison types : Qt::strong_ordering, Qt::weak_ordering and Qt::partial_ordering for three-way comparisons. These are Qt's C++17 back-ports of the C++20 standard ordering types.
Second, we've introduced several helper methods in <QtCore/QtCompare>, that will serve you to implement three-way comparison for your custom types in C++17. Notably, Qt::compareThreeWay() is our C++17 version of operator<=>() for built-in types. As for template types, we've provided qCompareThreeWay(). Breaking it down further:
- Qt::compareThreeWay()
This helper function provides a three-way comparison for built-in C++ types. When compiled with C++20 support, Qt::compareThreeWay() calls the spaceship operator directly, otherwise if you're using C++17, the function mimics the spaceship operator's behavior by handling the comparison internally and returning one of Qt's comparison types.
Please keep in mind that if you wish to perform comparisons on pointers using Qt::compareThreeWay(), you should wrap them into Qt::totally_ordered_wrapper, introduced in Qt 6.8. Otherwise, you may encounter unspecified behavior. If you feel curious about the UB cases, I invite you to read "Built-in pointer relational comparison" section. - qCompareThreeWay()
This is a template function that performs a three-way comparison. It is useful for generic code when you don't know anything about the types being compared. However, it is only available when compareThreeWay() is implemented for the (LeftType, RightType) pair or the reversed (RightType, LeftType) pair. So make sure you first provide compareThreeWay() helper function for your types.
In Qt 6.7, QDate, QTime, QDateTime, QTimeZone, and qfloat16 classes were updated to use modernized comparisons. In Qt 6.8, most of the Qt Core classes got support for C++20 comparison.
This was achieved by providing two helper functions within the class definition:
-
friend bool comparesEqual(LeftType lhs, RightType rhs)
comparesEqual() is used to implement operator==() in C++20 and also operator!=() in C++17 .
-
friend ReturnType compareThreeWay(LeftType lhs, RightType rhs);
compareThreeWay() is used to implement the four relational operators in C++17, or operator<=>() in C++20.
The ReturnType must be one of the Qt's comparison types.
Implementation examples
In this section, we will have a look at two examples of implementing three-way comparisons. This will help clarify which helper function is most suitable for your situation when modernizing comparisons.
-
Qt::compareThreeWay() example
For the sake of simplicity, we take one class MyWrapper which has int and float member variables. First, we have to provide our own helper function compareThreeWay(). After that, create your relational operators based on the result of the newly created compareThreeWay().
Make sure you take advantage of the Qt versions of the named comparison functions such as is_eq() for operator==() and is_lt() for operator<(). They convert the three-way-comparison result into a relational operator result. Note that they were introduced in C++20 but we implemented them for C++17 also.
class MyWrapper {
public:
constexpr MyWrapper(int int_val, float float_val): m_i(int_val), m_f(float_val) {}
private:
friend Qt::partial_ordering
compareThreeWay(const MyWrapper &lhs, const MyWrapper &rhs) noexcept
{
auto int_res = Qt::compareThreeWay(lhs.m_i, rhs.m_i);
if (is_neq(int_res))
return int_res;
auto float_res = Qt::compareThreeWay(lhs.m_f, rhs.m_f);
return float_res;
}
friend bool operator==(const MyWrapper &lhs, const MyWrapper &rhs) noexcept
{ return is_eq(compareThreeWay(lhs, rhs)); }
friend bool operator<(const MyWrapper &lhs, const MyWrapper &rhs) noexcept
{ return is_lt(compareThreeWay(lhs, rhs)); }
// same for other relational operators, if needed
int m_i = 0;
float m_f = 0;
};Please note that int_res type is Qt::strong_ordering, however float_res type is Qt::partial_ordering. So, the return type of compareThreeWay() must be Qt::partial_ordering.
-
qCompareThreeWay() example
As mentioned earlier, qCompareThreeWay() is mainly useful for comparing types in generic code. Imagine you have a template class as follows, and you need to compare two instances of this class based on two member variables:
template <typename Type1, typename Type2>
class TemplateClass
{
public:
Type1 t1;
Type2 t2;
auto compareThreeWay(const TemplateClass &lhs, const TemplateClass &rhs)
{
const auto res = qCompareThreeWay(lhs.t1, rhs.t1);
if (is_neq(res))
return res;
return qCompareThreeWay(lhs.t2, rhs.t2);
}
}Bear in mind that, if your template types are C++ built-in types, Qt::compareThreeWay() will be called. Otherwise, if they are custom ones they must have their friend function compareThreeWay() already implemented.
Note: Take a look at Qt::strong_ordering, Qt::weak_ordering, and Qt::partial_ordering to see the possible return values of qCompareThreeWay() and compareThreeWay() functions.
Conclusion
Thanks for reading this far . As mentioned earlier, more Qt Core classes will support the C++20 comparisons in future Qt releases. We encourage you to try modernizing comparisons in your own classes by using Qt::compareThreeWay() or qCompareThreeWay() when applicable.
I hope you find this blog post helpful. Let us know your thoughts below!
Blog Topics:
Comments
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.8 Now!
Download the latest release here: www.qt.io/download.
Qt 6.8 release focuses on technology trends like spatial computing & XR, complex data visualization in 2D & 3D, and ARM-based development for desktop.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.