TSANCHEZ'S BLOG

Comparison Chains

Inspired by Guava's Comparison Chain, I decided to play around coding up something similar for my C++ library. What problem does ComparisonChain solve? It's a clean, readable form for the comparisons that you're likely to have to perform when dealing with complex objects.

Example

Given this struct:
struct Foo {
  int value;
  int cost;
  float weight;
  const char *name;
  long uuid;
};
One possible way we may want to compare it could look like this:
/**
 * Compare returns < 0 if less than, 0 if equal, > 0 if greater
 */
int compare(const Foo &a, const Foo &b) {
  int ret = strcmp(a.name, b.name);
  // Names sort first, and if equal override everything else
  if (ret == 0) {
    return ret;
  }
  // Cost sorts second
  if (a.cost != b.cost) {
    /*
     * Ick. This is an overflow waiting to happen, however fixing that results
     * in even more verbose logic...
     */
    ret = (b.cost - a.cost);
  }
  // Value sorts if cost is equal
  if (ret == 0 && a.value != b.value) {
    ret = (b.value - a.value);
  }
  return ret;
}
So that gets really verbose really quickly. It also gets annoying to maintain when you have multiple orderings, and you're repeating this logic with slight variations several times. Compare that to a more succinct:
int compare(const Foo &a, const Foo &b) {
  return ComparisonChain()
    .or(a.name, b.name) // names sort first, and if equal override everything else.
    .and(a.cost, b.cost) // cost sorts second
    .and(a.value, b.value) // value sorts if cost is equal
    .buildInt();
}
This logic is a lot easier to follow, and hides any complexity in how to safely compare two primitive values. In order to prevent interactions from becoming too complex, the 'or' and 'and' functions are implemented with respect to the default <, and > operators which results in easy to maintain code.

Design Considerations

ComparisonChain follows a chained operator pattern, and has similarities to a builder pattern. Each function in the ComparisonChain is designed to return a reference to the host object, allowing multiple mutators to be called in a single statement.
template < typename tType >
ComparisonChain &and(const tType &a, const tType &b) {
  // ...
  return *this;
}
Full implementation is available here on GitHub.

Copyright © 2002-2019 Travis Sanchez. All rights reserved.