Search code examples
c++setstrict-weak-ordering

Strict weak ordering on pointer values


Consider the following struct:

struct Foo {
  const Bar* x;
  const Bar* y;

  Foo(const Bar* _x, const Bar* _y = nullptr) : x(_x), y(_y) { assert(x); }
}

How do we define a strict weak ordering on x and y so that the object can be used within a std::set? Note that y can be a null pointer. As mentioned in Using std::less with nullptr the behavior of std::less on null pointers is undefined unspecified. Will the following solution be sufficient?

bool operator<(const Foo& rhs) const {
  uintptr_t numX = reinterpret_cast<uintptr_t>(x);
  uintptr_t numY = reinterpret_cast<uintptr_t>(y);

  uintptr_t numRhsX = reinterpret_cast<uintptr_t>(rhs.x);
  uintptr_t numRhsY = reinterpret_cast<uintptr_t>(rhs.y);

  return std::tie(numX, numY) < std::tie(numRhsX, numRhsY);
}

EDIT: If not what is the proper way (e.g. how to combine std::less with std::tie)?


Solution

  • Using std::less<Bar*> is sufficient (but using operator< is not). The pointer specializations of std::less (as the accepted answer to "Using std::less with nullptr" points out) guarantee a total ordering. Comparison with nullptr is unspecified, meaning the standard does not impose a particular ordering, but std::less must still produce a total ordering (and for a given pointer p, p < nullptr necessarily produces the same value every time).

    Since a total ordering is stronger than a weak ordering, using std::less is sufficient in your case.

    EDIT: If not what is the proper way (e.g. how to combine std::less with std::tie)?

    There is no neat way, unfortunately. Since std::tie returns an std::tuple, and comparison on tuples is defined in terms of operator< on their values (rather than std::less), you can't really use std::tie here. To use std::less, you'd have to do it manually:

    bool operator<(const Foo& rhs) const {
      if (std::less<>{}(x, rhs.x))
        return true;
      if (std::less<>{}(rhs.x, x))
        return false;
      return std::less<>{}(y, rhs.y);
    }
    

    As an aside, your current implementation (reinterpreting the pointers as integers) also produces a total ordering (obviously, since you're comparing integers) but instead of unspecified behavior you'll have implementation-defined behavior (from the reinterpret_cast).