I have a C++ class for which I need to define a comparator that should consider the result of several potentially expensive methods. I do not want to cache the result of these methods for all the objects in my set, because the criteria with the highest priority are cheaper, and I expect the very expensive ones at the bottom to trigger only in rare cases.
If I had a cmp() function that returned respectively -1, 0, or 1 when the first argument is lesser, equal, or greater to the second, and with shortcut logical operators that preserve integers, I could easily write
int compare(const Class &rhs) const {
return cmp(expensive_method_a(), rhs.expensive_method_b()) ||
cmp(expensive_method_b(), rhs.expensive_method_b()) ||
...
}
Unfortunately I need to work with the < operator, so it becomes ugly, costly, and error-prone:
bool operator<(const Class &rhs) const {
return expensive_method_a() < rhs.expensive_method_a() ||
(expensive_method_a() == rhs.expensive_method_a() &&
(expensive_method_b() < rhs.expensive_method_b() ||
(expensive_method_b() == rhs.expensive_method_b() &&
(...
))))
}
Or alternatively, less costly but still pretty ugly:
bool operator<(const Class &rhs) const {
auto al = expensive_method_a(), ar = rhs.expensive_method_a();
if (al != ar) return al < ar;
auto bl = expensive_method_b(), br = rhs.expensive_method_b();
if (bl != br) return bl < br;
I've read about std::tie at This other question, but if I understand correctly, the tie would evaluate all my methods before starting the comparaison, and I want these arguments to be lazily evaluated.
I thought about defining a preprocessor macro such as this:
#define CUT_COMPARE(a,b) { auto _x = (a); auto _y = (b); if (_x != _y) return (_x < _y); }
That I would use like:
bool operator<(const Class &rhs) const {
CUT_COMPARE(expensive_method_a(), rhs.expensive_method_a());
CUT_COMPARE(expensive_method_b(), rhs.expensive_method_b());
...
}
hoping that the braces would enclose my _x
and _y
in a private scope, but alas, clang++
complains of multiple definitions of _x
and _y
.
Is there a prettier way around this ?